home *** CD-ROM | disk | FTP | other *** search
Text File | 1992-05-26 | 112.9 KB | 2,600 lines |
- ;;; -*- Mode:Lisp; Package:CLIO-OPEN; Syntax:COMMON-LISP; Base:10; Lowercase:T -*-
-
-
- ;;;----------------------------------------------------------------------------------+
- ;;; |
- ;;; TEXAS INSTRUMENTS INCORPORATED |
- ;;; P.O. BOX 149149 |
- ;;; AUSTIN, TEXAS 78714 |
- ;;; |
- ;;; Copyright (C) 1989, 1990 Texas Instruments Incorporated. |
- ;;; |
- ;;; Permission is granted to any individual or institution to use, copy, modify, and |
- ;;; distribute this software, provided that this complete copyright and permission |
- ;;; notice is maintained, intact, in all copies and supporting documentation. |
- ;;; |
- ;;; Texas Instruments Incorporated provides this software "as is" without express or |
- ;;; implied warranty. |
- ;;; |
- ;;;----------------------------------------------------------------------------------+
-
-
- ;;;
- ;;; Form, a layout composite with fancy constraints.
- ;;;
- ;;; Work remaining:
- ;;;
- ;;; 1. To-link traversals (all the traversals to date use the from-links).
- ;;; [This one's done, except for some refinement and testing. - pf]
- ;;; 2. When taking a child's size, check its state and use 0 if it isn't
- ;;; :mapped.
- ;;; 3. Compromise child-resize in manage-geometry-hard-case when the Form
- ;;; is only granted part of its own resize request (currently it punts).
- ;;;
- ;;; Open issues or places to work on are marked with +++.
-
- (in-package "CLIO-OPEN")
-
- (export '(
- form
- make-form
- make-horizontal-link
- make-vertical-link
- form-max-height
- form-max-width
- form-min-height
- form-min-width
- link-from
- link-to
- link-orientation
- link-attach-from
- link-attach-to
- link-length
- link-maximum
- link-minimum
- link-update
- find-link
- )
- 'clio-open)
-
-
- ;;;
- ;;; The Form contact itself. Horizontal-links and vertical-links normally
- ;;; store lists of links that connect directly to the Form, but they can
- ;;; also be passed as initargs to make-form in a form (no pun) that specifies
- ;;; how to link the Form and its children (see resolve-initial-links for
- ;;; details).
-
- (defcontact form (core composite)
- ((horizontal-links :type list
- :initform nil
- :accessor form-horizontal-links
- :initarg :horizontal-links)
- (vertical-links :type list
- :initform nil
- :accessor form-vertical-links
- :initarg :vertical-links))
- (:resources (border-width :initform 0))
- (:constraints (:max-height :type (or card16 (member :infinite)))
- (:max-width :type (or card16 (member :infinite)))
- (:min-height :type card16)
- (:min-width :type card16))
- (:documentation "A layout using form constraints."))
-
-
- (defun make-form (&rest initargs &key &allow-other-keys)
- (apply #'make-contact 'form initargs))
-
-
- ;;;
- ;;; Link structure and functions. Placement constraints among the children
- ;;; of a Form are handled by attaching links between pairs of children and
- ;;; between the Form and a child. These links define a graph that is traversed
- ;;; to determine the Form's preferred-size, or the amount of shrink or stretch
- ;;; during a resizing (traversal functions are further below). Links are not
- ;;; too different from TeX's "glue" boxes, with the additional generality that
- ;;; they can be attached to either edge or the center of a contact and that they
- ;;; work in two dimensions rather than a one-dimensional line.
-
- ;; Each link between two contacts is one of these (the same object is
- ;; stored with both contacts). Length is measured between FROM and
- ;; TO. The attach-points are on the contact named, and may be
- ;; any of :left, :right, :top, :bottom, or :center.
- ;;
- ;; This guy is a class chiefly for portability: the type-checking and layout
- ;; changing could just as easily be defined in a defsetf on a defstruct accessor,
- ;; but there isn't a portable way to set a structure slot other than SETF on the
- ;; accessor, so it couldn't be done portably. If this turns out to be inefficient,
- ;; we may want to change back, bearing in mind that some places in this code want
- ;; to change slot values without doing the change-layout (manage-geometry, for one).
- (defclass link ()
- ((orientation :initform nil
- :reader link-orientation
- :initarg :orientation)
- (from :initform nil
- :initarg :from)
- (attach-from :initform nil
- :reader link-attach-from
- :initarg :attach-from)
- (to :initform nil
- :initarg :to)
- (attach-to :initform nil
- :reader link-attach-to
- :initarg :attach-to)
- (length :initform 0
- :reader link-length
- :initarg :length)
- (minimum :initform nil
- :reader link-minimum
- :initarg :minimum)
- (maximum :initform nil
- :reader link-maximum
- :initarg :maximum)
- (tentative-length :initform nil)
- (implicit-p :initform nil
- :accessor link-implicit-p)))
-
- ;; These two readers are defined specially so they can error-check.
- (defmethod link-from ((link link))
- (with-slots (from) link
- (or from
- (error "Link ~S is not valid." link))))
-
- (defmethod link-to ((link link))
- (with-slots (to) link
- (or to
- (error "Link ~S is not valid." link))))
-
- (defmethod (setf link-attach-from) (attach-from (link link))
- (ecase (link-orientation link)
- (:horizontal
- (check-type attach-from (member :left :right :center)))
- (:vertical
- (check-type attach-from (member :center :top :bottom))))
- (setf (slot-value link 'attach-from) attach-from)
- (link-update-change-layout link)
- attach-from)
-
- (defmethod (setf link-attach-to) (attach-to (link link))
- (ecase (link-orientation link)
- (:horizontal
- (check-type attach-to (member :left :right :center)))
- (:vertical
- (check-type attach-to (member :center :top :bottom))))
- (setf (slot-value link 'attach-to) attach-to)
- (link-update-change-layout link)
- attach-to)
-
- (defmethod (setf link-length) (length (link link))
- (check-type length int16)
- (setf (slot-value link 'length) length)
- (link-update-change-layout link)
- length)
-
- (defmethod (setf link-maximum) (maximum (link link))
- (check-type maximum (or int16 (member :infinite)))
- (setf (slot-value link 'maximum) maximum)
- (link-update-change-layout link)
- maximum)
-
- (defmethod (setf link-minimum) (minimum (link link))
- (check-type minimum int16)
- (setf (slot-value link 'minimum) minimum)
- (link-update-change-layout link)
- minimum)
-
- ;; Let the tentative length default to the true length.
- (defmacro link-tentative-length (link)
- `(or (slot-value (the link ,link) 'tentative-length)
- (link-length ,link)))
-
- (defsetf link-tentative-length (link) (tentative-length)
- `(setf (slot-value ,link 'tentative-length) ,tentative-length))
-
-
- (defun link-update (link &key length minimum maximum attach-from attach-to)
- "Make multiple changes to a link, as if by setf'ing all the fields given."
- (declare (type link link))
- (check-type link link)
- (check-type length (or null int16))
- (check-type minimum (or null int16))
- (check-type maximum (or null int16 (member :infinite)))
- (check-type attach-from (or null (member :left :right :center :top :bottom)))
- (check-type attach-to (or null (member :left :right :center :top :bottom)))
- (with-slots ((link-length length)
- (link-minimum minimum)
- (link-maximum maximum)
- (link-attach-from attach-from)
- (link-attach-to attach-to))
- link
- (when length
- (setq link-length length))
- (when minimum
- (setq link-minimum minimum))
- (when maximum
- (setq link-maximum maximum))
- (when attach-from
- (setq link-attach-from attach-from))
- (when attach-to
- (setq link-attach-to attach-to)))
-
- (link-update-change-layout link))
-
- ;; Once the changes are complete, call change-layout to make it happen.
- ;; This doesn't consistency-check the parentage, since that is done elsewhere
- ;; (make-horizontal-link, make-vertical-link) and not changed here.
- (defun link-update-change-layout (link)
- (let ((form (if (or (eq (contact-parent (link-from link))
- (contact-parent (link-to link)))
- (eq (contact-parent (link-from link))
- (link-to link)))
- (contact-parent (link-from link))
- (contact-parent (link-to link)))))
- (change-layout form)))
-
- (defun find-link (from to orientation &optional form-attach-point)
- "Find the link between FROM and TO with the orientation ORIENTATION.
- Will find the link regardless of the ordering of FROM and TO. Returns
- NIL if no link found. FORM-ATTACH-POINT is the attach-point on the Form
- itself, if one of the contacts is the Form and one is a child."
- (check-type from contact)
- (check-type to contact)
- (check-type orientation (member :horizontal :vertical))
- (check-type form-attach-point (or null (member :left :right :center :top :bottom)))
- (assert (not (eq from to)) () "A contact may not be linked to itself.")
- (assert (or (eq (contact-parent from) (contact-parent to))
- (eq from (contact-parent to))
- (eq to (contact-parent from)))
- ()
- "Two linked contacts must either be children of the same Form, or the Form and one of its children.")
- (let ((link-list (if (or (eq (contact-parent from) (contact-parent to))
- (eq (contact-parent from) to))
- (if (eq orientation :horizontal)
- (contact-constraint from :horizontal-links)
- (contact-constraint from :vertical-links))
- (if (eq orientation :horizontal)
- (contact-constraint to :horizontal-links)
- (contact-constraint to :vertical-links)))))
- (dolist (link link-list nil)
- (when (and (eq (link-orientation link) orientation)
- (or (and (eq (link-from link) from)
- (eq (link-to link) to)
- (or (null form-attach-point)
- (cond ((eq from (contact-parent to))
- (eq form-attach-point (link-attach-from link)))
- ((eq to (contact-parent from))
- (eq form-attach-point (link-attach-to link)))
- (:else
- t))))
- (and (eq (link-to link) from)
- (eq (link-from link) to)
- (or (null form-attach-point)
- (cond ((eq from (contact-parent to))
- (eq form-attach-point (link-attach-to link)))
- ((eq to (contact-parent from))
- (eq form-attach-point (link-attach-from link)))
- (:else
- t))))))
- (return link)))))
-
- ;; Destroying a link means removing its connections. We NIL out its contacts
- ;; to flag later improper use.
- (defmethod destroy ((link link))
- (with-slots (from to) link
- (if (eq from (contact-parent to))
- (setf (form-horizontal-links from)
- (delete link (form-horizontal-links from)))
- (setf (contact-constraint from :horizontal-links)
- (delete link (contact-constraint from :horizontal-links))))
- (if (eq (contact-parent from) to)
- (setf (form-horizontal-links to)
- (delete link (form-horizontal-links to)))
- (setf (contact-constraint to :horizontal-links)
- (delete link (contact-constraint to :horizontal-links))))
- (setq from nil
- to nil)))
-
- ;; Only allow one link of a given orientation between two contacts.
- ;;
- ;; This function is a little hairy to treat the different attach-points of the top-level
- ;; Form as if they were separate contacts for the sake of this test. It is allowed to
- ;; have multiple links between the Form and a single child if they all attach at different
- ;; places, eg, from the :left of the Form to the :left of the child and from the :right
- ;; of the child to the :right of the Form. So, the test is that if the FROM and TO fields
- ;; of the links are the same, the links are equal if either both contacts are children,
- ;; or one is the Form and the Form attach-points are the same. The test is similar when
- ;; the FROM and TO fields are crossed (from-1 is to-2 and vice versa).
- (defun link-equal (link-1 link-2)
- (let ((from-1 (link-from link-1))
- (from-2 (link-from link-2))
- (to-1 (link-to link-1))
- (to-2 (link-to link-2)))
- (and (eq (link-orientation link-1)
- (link-orientation link-2))
- (or (and (eq from-1 from-2)
- (eq to-1 to-2)
- (or (eq (contact-parent from-1) (contact-parent to-1))
- (if (eq from-1 (contact-parent to-1))
- (eq (link-attach-from link-1) (link-attach-from link-2))
- (eq (link-attach-to link-1) (link-attach-to link-2)))))
- (and (eq from-1 to-2)
- (eq from-2 to-1)
- (or (eq (contact-parent from-1) (contact-parent to-1))
- (if (eq from-1 (contact-parent to-1))
- (eq (link-attach-from link-1) (link-attach-to link-2))
- (eq (link-attach-to link-1) (link-attach-from link-2)))))))))
-
- (defun make-horizontal-link (&key from to
- (minimum 0) (length minimum) (maximum :infinite)
- (attach-from :right) (attach-to :left))
- " Add a horizontal link between two contacts. The contacts must either
- be children of the same Form, or the Form and one of its children. FROM
- is the \"left\" contact -- lengths are positive when FROM is to the left
- of TO, negative otherwise.
- ATTACH-FROM and ATTACH-TO indicate where the link is attached to the
- FROM and TO contacts, respectively, and must be one of :LEFT, :RIGHT, or
- :CENTER, referring to the left or right edge or the center of the contact.
- LENGTH, MINIMUM, and MAXIMUM define the length of the link and its
- range of values. All may be any INT16; MAXIMUM may also be :INFINITE."
-
- (check-type from contact)
- (check-type to contact)
- (check-type attach-from (member :left :center :right))
- (check-type attach-to (member :left :center :right))
- (check-type length int16)
- (check-type minimum int16)
- (check-type maximum (or int16 (member :infinite)))
- (assert (not (eq from to)) () "A contact may not be linked to itself.")
- (assert (or (eq (contact-parent from) (contact-parent to))
- (eq from (contact-parent to))
- (eq to (contact-parent from)))
- ()
- "Two linked contacts must either be children of the same Form, or the Form and one of its children.")
-
- ;; The flags left-form-p and right-form-p are needed because a child contact's
- ;; links are kept on its contact-constraints, while the parent Form's links
- ;; are kept in its slot variables. This distinction will crop up frequently.
- ;; When true, the contact indicated is the parent Form.
- (let ((left-form-p (eq from (contact-parent to)))
- (right-form-p (eq to (contact-parent from)))
- (link (make-instance 'link
- :orientation :horizontal
- :from from
- :to to
- :attach-from attach-from
- :attach-to attach-to
- :length length
- :minimum minimum
- :maximum maximum)))
-
- ;; If there already exists a link between these two contacts, remove it
- ;; superseding it with this one. Note that link-equal special-cases links
- ;; to the Form to allow links from the left of the Form to the left of the
- ;; child, thence from the right of the child to the right of the Form.
- (cond (left-form-p
- (setf (form-horizontal-links from)
- (delete link (form-horizontal-links from) :test #'link-equal))
- (setf (contact-constraint to :horizontal-links)
- (delete link (contact-constraint to :horizontal-links) :test #'link-equal)))
- (right-form-p
- (setf (contact-constraint from :horizontal-links)
- (delete link (contact-constraint from :horizontal-links) :test #'link-equal))
- (setf (form-horizontal-links to)
- (delete link (form-horizontal-links to) :test #'link-equal)))
- (:else
- (setf (contact-constraint from :horizontal-links)
- (delete link (contact-constraint from :horizontal-links) :test #'link-equal))
- (setf (contact-constraint to :horizontal-links)
- (delete link (contact-constraint to :horizontal-links) :test #'link-equal))))
-
- ;; Save the link on the appropriate list.
- (if left-form-p
- (push link (form-horizontal-links from))
- (push link (contact-constraint from :horizontal-links)))
- (if right-form-p
- (push link (form-horizontal-links to))
- (push link (contact-constraint to :horizontal-links)))
- link))
-
- (defun make-vertical-link (&key from to
- (minimum 0) (length minimum) (maximum :infinite)
- (attach-from :bottom) (attach-to :top))
- " Add a vertical link between two contacts. The contacts must either
- be children of the same Form, or the Form and one of its children. FROM
- is the \"top\" contact -- lengths are positive when FROM is above
- TO, negative otherwise.
- ATTACH-FROM and ATTACH-TO indicate where the link is attached to the
- FROM and TO, respectively, and must be one of :TOP, :BOTTOM, or
- :CENTER, referring to the top or bottom edge or the center of the contact.
- LENGTH, MINIMUM, and MAXIMUM define the length of the link
- and its range of values. All may be any INT16; MAXIMUM may also be
- :INFINITE."
- (check-type from contact)
- (check-type to contact)
- (check-type attach-from (member :top :center :bottom))
- (check-type attach-to (member :top :center :bottom))
- (check-type length int16)
- (check-type minimum int16)
- (check-type maximum (or int16 (member :infinite)))
- (assert (not (eq from to)) () "A contact may not be linked to itself.")
- (assert (or (eq (contact-parent from) (contact-parent to))
- (eq from (contact-parent to))
- (eq to (contact-parent from)))
- ()
- "Two linked contacts must either be children of the same Form, or the Form and one of its children.")
-
- ;; The flags top-form-p and bottom-form-p are needed because a child contact's
- ;; links are kept on its contact-constraints, while the parent Form's links
- ;; are kept in its slot variables. This distinction will crop up frequently.
- ;; When true, the contact indicated is the parent Form.
- (let ((top-form-p (eq from (contact-parent to)))
- (bottom-form-p (eq to (contact-parent from)))
- (link (make-instance 'link
- :orientation :vertical
- :from from
- :to to
- :attach-from attach-from
- :attach-to attach-to
- :length length
- :minimum minimum
- :maximum maximum)))
-
- ;; If there already exists a link between these two contacts, remove it
- ;; superseding it with this one. Note that link-equal special-cases links
- ;; to the Form to allow links from the top of the Form to the top of the
- ;; child, thence from the bottom of the child to the bottom of the Form.
- (cond (top-form-p
- (setf (form-vertical-links from)
- (delete link (form-vertical-links from) :test #'link-equal))
- (setf (contact-constraint to :vertical-links)
- (delete link (contact-constraint to :vertical-links) :test #'link-equal)))
- (bottom-form-p
- (setf (contact-constraint from :vertical-links)
- (delete link (contact-constraint from :vertical-links) :test #'link-equal))
- (setf (form-vertical-links to)
- (delete link (form-vertical-links to) :test #'link-equal)))
- (:else
- (setf (contact-constraint from :vertical-links)
- (delete link (contact-constraint from :vertical-links) :test #'link-equal))
- (setf (contact-constraint to :vertical-links)
- (delete link (contact-constraint to :vertical-links) :test #'link-equal))))
-
- ;; Save the link on the appropriate list.
- (if top-form-p
- (push link (form-vertical-links from))
- (push link (contact-constraint from :vertical-links)))
- (if bottom-form-p
- (push link (form-vertical-links to))
- (push link (contact-constraint to :vertical-links)))
- link))
-
-
- ;;;
- ;;; Constraints. These functions are the advertised interface for accessing
- ;;; and modifying constraints on the size of the children contacts, in addition
- ;;; to the ability to specify them as initargs to make-contact when making the
- ;;; children. The maximum and minimum height and width, if unspecified, will
- ;;; be the current height and width.
-
- (defun form-max-height (contact)
- (or (contact-constraint contact :max-height)
- (contact-height contact)))
- (defsetf form-max-height setf-form-max-height)
- (defun setf-form-max-height (contact new-value)
- (check-type new-value (or null card16 (member :infinite)))
- (setf (contact-constraint contact :max-height) new-value))
-
- (defun form-max-width (contact)
- (or (contact-constraint contact :max-width)
- (contact-width contact)))
- (defsetf form-max-width setf-form-max-width)
- (defun setf-form-max-width (contact new-value)
- (check-type new-value (or null card16 (member :infinite)))
- (setf (contact-constraint contact :max-width) new-value))
-
- (defun form-min-height (contact)
- (or (contact-constraint contact :min-height)
- (contact-height contact)))
- (defsetf form-min-height setf-form-min-height)
- (defun setf-form-min-height (contact new-value)
- (check-type new-value (or null card16))
- (setf (contact-constraint contact :min-height) new-value))
-
- (defun form-min-width (contact)
- (or (contact-constraint contact :min-width)
- (contact-width contact)))
- (defsetf form-min-width setf-form-min-width)
- (defun setf-form-min-width (contact new-value)
- (check-type new-value (or null card16))
- (setf (contact-constraint contact :min-width) new-value))
-
-
- ;; Abstractions for various things placed on the window-plist of each child.
- ;;
- ;; Form-tick is just a flag that is set on each child as it is visited in the
- ;; resize process. If an attempt is made to move a child that has already
- ;; been moved, it's an indication that the constraints are inconsistent, and
- ;; an error is signalled.
- ;;
- ;; The "tentative" quantities are here for two reasons: (1) They allow trying out
- ;; sizes and placements without really changing anything, which we use in
- ;; manage-geometry to shuffle things around when necessary. (2) For resizing,
- ;; it's part of an efficiency hack: The algorithm for a Form resize is to resize
- ;; the children, determining the maximum stretch or shrink across the link graph
- ;; and apportioning the size change to the children according to their
- ;; constraints, then to move the children so they satisfy the placement
- ;; constraints of the links. The hack is that the resizes and moves are faked --
- ;; the new values are placed on the window-plists of the children, using the
- ;; "tentative" accessors below, and are used by subsequent steps of the algorithm.
- ;; Once all the new values are computed, one pass through the children combines
- ;; the new width, height, x, and y values into a single move and resize within a
- ;; with-state, thereby limiting server requests to a maximum of one per child.
-
- (defmacro form-tick (contact)
- `(getf (window-plist ,contact) 'form-tick))
-
- (defmacro contact-tentative-width (contact)
- `(or (getf (window-plist ,contact) 'tentative-width)
- (contact-width ,contact)))
- (defsetf contact-tentative-width (contact) (new-val)
- `(setf (getf (window-plist ,contact) 'tentative-width) ,new-val))
-
- (defmacro contact-tentative-height (contact)
- `(or (getf (window-plist ,contact) 'tentative-height)
- (contact-height ,contact)))
- (defsetf contact-tentative-height (contact) (new-val)
- `(setf (getf (window-plist ,contact) 'tentative-height) ,new-val))
-
- (defmacro contact-tentative-x (contact)
- `(or (getf (window-plist ,contact) 'tentative-x)
- (contact-x ,contact)))
- (defsetf contact-tentative-x (contact) (new-val)
- `(setf (getf (window-plist ,contact) 'tentative-x) ,new-val))
-
- (defmacro contact-tentative-y (contact)
- `(or (getf (window-plist ,contact) 'tentative-y)
- (contact-y ,contact)))
- (defsetf contact-tentative-y (contact) (new-val)
- `(setf (getf (window-plist ,contact) 'tentative-y) ,new-val))
-
- ;; These two are similar in concept, but are used during change-layout
- ;; so manage-geometry can experiment with various Form sizes without
- ;; really doing the change.
- (defmacro form-projected-width (contact)
- `(or (getf (window-plist ,contact) 'form-projected-width)
- (contact-width ,contact)))
- (defsetf form-projected-width (contact) (new-val)
- `(setf (getf (window-plist ,contact) 'form-projected-width) ,new-val))
-
- (defmacro form-projected-height (contact)
- `(or (getf (window-plist ,contact) 'form-projected-height)
- (contact-height ,contact)))
- (defsetf form-projected-height (contact) (new-val)
- `(setf (getf (window-plist ,contact) 'form-projected-height) ,new-val))
-
-
- ;;;
- ;;; Traversal functions. Lots of the important work in placing and sizing
- ;;; the children of a Form happens right here. The next 24 functions
- ;;; traverse the graph defined by the links between the Form and the children
- ;;; to determine the Form's preferred width and height and the allowable stretch
- ;;; and shrink both horizontally and vertically. Their use is explained below,
- ;;; around place-and-size-children.
- ;;;
- ;;; The pattern is pretty much the same for each of the six pairs of functions:
- ;;; The "top-level" function looks at all the links attached to the Form that
- ;;; have the Form in the FROM position, and maximises the desired quantity
- ;;; over all paths through the graph that start with those links. The "path"
- ;;; function recurses through the children contacts and their links until it
- ;;; reaches the Form again, also maximising as it goes. The two "stretch"
- ;;; functions vary a bit in that they pass multiple values around -- the maximum
- ;;; values of child sizes and link lengths are allowed to be :infinite, so the
- ;;; "stretch" functions maximise primarily over the number of :infinites, and
- ;;; secondarily over the numerical values.
-
- ;; Find the desired Form width, given the existing sizes of children and links.
- ;; A path from the center of the Form through children to the Form's left or right
- ;; defines half the width, and thus implies the whole width. So, if the given
- ;; path went from :left to :center or :right to :center, double it for the Form width.
- (defun find-form-ideal-width (form)
- (dolist (contact (composite-children form))
- (setf (form-tick contact) nil))
- (let ((max-width 0))
- (dolist (link (form-horizontal-links form))
- (when (eq form (link-from link))
- ;; Path-value is the length of the link, corrected according to where
- ;; it attaches to the next contact (ie, the distance to the left edge
- ;; of the next contact), plus the value of the maximum path starting
- ;; at that contact.
- (let ((path-value (+ (link-length link)
- (link-horizontal-attach-to-correction link)
- (find-path-ideal-width (link-to link) form))))
- (when (eq (link-attach-from link) :center)
- (setq path-value (* 2 path-value)))
- (setq max-width (max max-width path-value)))
- (setf (form-tick (link-to link)) t)))
- ;; Now do graphs rooted on the "to" side of the Form, not reachable from
- ;; the "from" side.
- (dolist (link (form-horizontal-links form))
- (when (and (eq form (link-to link))
- (null (form-tick (link-from link))))
- (let ((path-value (+ (link-length link)
- (link-horizontal-attach-from-correction link)
- (find-path-ideal-width (link-from link) form t))))
- (when (eq (link-attach-to link) :center)
- (setq path-value (* 2 path-value)))
- (setq max-width (max max-width path-value)))))
- max-width))
-
- ;; The width down a given path is the width of the contact and its borders, plus
- ;; the maximum path value for all paths using its links. Link lengths are corrected
- ;; for attachments other than right-edge to left-edge.
- (defun find-path-ideal-width (contact top-level-form &optional to-links-p)
- (if (eq contact top-level-form)
- 0 ; Back at the parent Form, end of path.
- (+ (contact-width contact)
- (contact-border-width contact)
- (contact-border-width contact)
- (let ((max-width 0))
- (dolist (link (contact-constraint contact :horizontal-links)
- max-width)
- (when (eq contact (if to-links-p
- (link-to link)
- (link-from link)))
- (let* ((next-contact (if to-links-p
- (link-from link)
- (link-to link)))
- (path-value (+ (if to-links-p
- (link-horizontal-attach-to-correction link)
- (link-horizontal-attach-from-correction link))
- (link-length link)
- ;; Don't compensate when attaching to form.
- (if (eq next-contact top-level-form)
- 0
- (if to-links-p
- (link-horizontal-attach-from-correction link)
- (link-horizontal-attach-to-correction link)))
- (find-path-ideal-width next-contact top-level-form to-links-p))))
- (setq max-width (max max-width path-value)))))))))
-
- (defun link-horizontal-attach-to-correction (link)
- (let ((next-contact (link-to link)))
- (ecase (link-attach-to link)
- (:left 0)
- (:center (- (+ (round (contact-width next-contact) 2)
- (contact-border-width next-contact))))
- (:right (- (+ (contact-width next-contact)
- (contact-border-width next-contact)
- (contact-border-width next-contact)))))))
-
- (defun link-horizontal-attach-from-correction (link)
- (let ((contact (link-from link)))
- (ecase (link-attach-from link)
- (:left (- (+ (contact-width contact)
- (contact-border-width contact)
- (contact-border-width contact))))
- (:center (- (+ (round (contact-width contact) 2)
- (contact-border-width contact))))
- (:right 0))))
-
-
- ;; Find the desired Form height, given the existing sizes of children and links.
- ;; A path from the center of the Form through children to the Form's top or bottom
- ;; defines half the height, and thus implies the whole height. So, if the given
- ;; path went from :top to :center or :bottom to :center, double it for the Form height.
- (defun find-form-ideal-height (form)
- (dolist (contact (composite-children form))
- (setf (form-tick contact) nil))
- (let ((max-height 0))
- (dolist (link (form-vertical-links form))
- (when (eq form (link-from link))
- ;; Path-value is the length of the link, corrected according to where
- ;; it attaches to the next contact, plus the value of the maximum path
- ;; starting at that contact.
- (let ((path-value (+ (link-length link)
- (link-vertical-attach-to-correction link)
- (find-path-ideal-height (link-to link) form))))
- (when (eq (link-attach-from link) :center)
- (setq path-value (* 2 path-value)))
- (setq max-height (max max-height path-value))
- (setf (form-tick (link-to link)) t))))
- (dolist (link (form-vertical-links form))
- (when (and (eq form (link-to link))
- (null (form-tick (link-from link))))
- ;; Path-value is the length of the link, corrected according to where
- ;; it attaches to the next contact, plus the value of the maximum path
- ;; starting at that contact.
- (let ((path-value (+ (link-length link)
- (link-vertical-attach-from-correction link)
- (find-path-ideal-height (link-from link) form t))))
- (when (eq (link-attach-to link) :center)
- (setq path-value (* 2 path-value)))
- (setq max-height (max max-height path-value)))))
- max-height))
-
- ;; The height down a given path is the height of the contact and its borders, plus
- ;; the maximum path value for all paths using its links. Link lengths are corrected
- ;; for attachments other than bottom-edge to top-edge.
- (defun find-path-ideal-height (contact top-level-form &optional to-links-p)
- (if (eq contact top-level-form)
- 0
- (+ (contact-height contact)
- (contact-border-width contact)
- (contact-border-width contact)
- (let ((max-height 0))
- (dolist (link (contact-constraint contact :vertical-links)
- max-height)
- (when (eq contact (if to-links-p (link-to link) (link-from link)))
- (let* ((next-contact (if to-links-p (link-from link) (link-to link)))
- (path-value (+ (if to-links-p
- (link-vertical-attach-to-correction link)
- (link-vertical-attach-from-correction link))
- (link-length link)
- ;; Don't compensate when attaching to form.
- (if (eq next-contact top-level-form)
- 0
- (if to-links-p
- (link-vertical-attach-from-correction link)
- (link-vertical-attach-to-correction link)))
- (find-path-ideal-height next-contact top-level-form to-links-p))))
- (setq max-height (max max-height path-value)))))))))
-
- (defun link-vertical-attach-to-correction (link)
- (let ((next-contact (link-to link)))
- (ecase (link-attach-to link)
- (:top 0)
- (:center (- (+ (round (contact-height next-contact) 2)
- (contact-border-width next-contact))))
- (:bottom (- (+ (contact-height next-contact)
- (contact-border-width next-contact)
- (contact-border-width next-contact)))))))
-
- (defun link-vertical-attach-from-correction (link)
- (let ((contact (link-from link)))
- (ecase (link-attach-from link)
- (:top (- (+ (contact-height contact)
- (contact-border-width contact)
- (contact-border-width contact))))
- (:center (- (+ (round (contact-height contact) 2)
- (contact-border-width contact))))
- (:bottom 0))))
-
-
- ;; Stretch is a little hairy. The ultimate result will be two values, the maximum
- ;; number of :infinites and the largest numerical value associated with that many
- ;; :infinites. When scaling sizes and link-lengths, the size increase is apportioned
- ;; among the contacts and links according to their "maximum" constraints. If there
- ;; are any :infinites in the stretch value, only those contacts and links with :infinite
- ;; as their maximum will stretch, because :infinite is by definition much stretchier
- ;; than any numerical maximum.
- (defun find-form-horizontal-stretch (form)
- (dolist (contact (composite-children form))
- (setf (form-tick contact) nil))
- (let ((max-stretch-value 0)
- (max-stretch-inf 0))
- (dolist (link (form-horizontal-links form))
- (when (eq form (link-from link))
- ;; Find the values for a given path, then add in the values for the link.
- (multiple-value-bind (path-value path-inf)
- (find-path-horizontal-stretch (link-to link) form)
- (if (eq (link-maximum link) :infinite)
- (incf path-inf)
- (incf path-value (- (link-maximum link)
- (link-length link))))
- ;; Maximise the number of :infinites, or the numerical value if :infinites
- ;; are equal.
- (cond ((> path-inf max-stretch-inf)
- (setq max-stretch-value path-value
- max-stretch-inf path-inf))
- ((= path-inf max-stretch-inf)
- (setq max-stretch-value (max path-value max-stretch-value))))
- (setf (form-tick (link-to link)) t))))
- ;; Now do the isolated to-links.
- (dolist (link (form-horizontal-links form))
- (when (and (eq form (link-to link))
- (null (form-tick (link-from link))))
- ;; Find the values for a given path, then add in the values for the link.
- (multiple-value-bind (path-value path-inf)
- (find-path-horizontal-stretch (link-from link) form t)
- (if (eq (link-maximum link) :infinite)
- (incf path-inf)
- (incf path-value (- (link-maximum link)
- (link-length link))))
- ;; Maximise the number of :infinites, or the numerical value if :infinites
- ;; are equal.
- (cond ((> path-inf max-stretch-inf)
- (setq max-stretch-value path-value
- max-stretch-inf path-inf))
- ((= path-inf max-stretch-inf)
- (setq max-stretch-value (max path-value max-stretch-value)))))))
- (values max-stretch-value
- max-stretch-inf)))
-
- (defun find-path-horizontal-stretch (contact top-level-form &optional to-links-p)
- (if (eq contact top-level-form)
- (values 0 0)
- (let ((max-stretch-value 0)
- (max-stretch-inf 0))
- (dolist (link (contact-constraint contact :horizontal-links))
- ;; Find the values for a given path, then add in the values for the link.
- (when (eq contact (if to-links-p (link-to link) (link-from link)))
- (multiple-value-bind (path-value path-inf)
- (find-path-horizontal-stretch (if to-links-p (link-from link) (link-to link))
- top-level-form
- to-links-p)
- (if (eq (link-maximum link) :infinite)
- (incf path-inf)
- (incf path-value (- (link-maximum link)
- (link-length link))))
- ;; Maximise the number of :infinites, or the numerical value if :infinites
- ;; are equal.
- (cond ((> path-inf max-stretch-inf)
- (setq max-stretch-value path-value
- max-stretch-inf path-inf))
- ((= path-inf max-stretch-inf)
- (setq max-stretch-value (max path-value max-stretch-value)))))))
- ;; Add in the values for the contact.
- (if (eq (form-max-width contact) :infinite)
- (setq max-stretch-inf (1+ max-stretch-inf))
- (setq max-stretch-value (+ (- (form-max-width contact)
- (contact-width contact))
- max-stretch-value)))
- (values max-stretch-value
- max-stretch-inf))))
-
- ;; See comments in front of find-form-horizontal-stretch.
- (defun find-form-vertical-stretch (form)
- (dolist (contact (composite-children form))
- (setf (form-tick contact) nil))
- (let ((max-stretch-value 0)
- (max-stretch-inf 0))
- (dolist (link (form-vertical-links form))
- (when (eq form (link-from link))
- ;; Find the values for a given path, then add in the values for the link.
- (multiple-value-bind (path-value path-inf)
- (find-path-vertical-stretch (link-to link) form)
- (if (eq (link-maximum link) :infinite)
- (incf path-inf)
- (incf path-value (- (link-maximum link)
- (link-length link))))
- ;; Maximise the number of :infinites, or the numerical value if :infinites
- ;; are equal.
- (cond ((> path-inf max-stretch-inf)
- (setq max-stretch-value path-value
- max-stretch-inf path-inf))
- ((= path-inf max-stretch-inf)
- (setq max-stretch-value (max path-value max-stretch-value))))
- (setf (form-tick (link-to link)) t))))
- (dolist (link (form-vertical-links form))
- (when (and (eq form (link-to link))
- (null (form-tick (link-from link))))
- ;; Find the values for a given path, then add in the values for the link.
- (multiple-value-bind (path-value path-inf)
- (find-path-vertical-stretch (link-from link) form t)
- (if (eq (link-maximum link) :infinite)
- (incf path-inf)
- (incf path-value (- (link-maximum link)
- (link-length link))))
- ;; Maximise the number of :infinites, or the numerical value if :infinites
- ;; are equal.
- (cond ((> path-inf max-stretch-inf)
- (setq max-stretch-value path-value
- max-stretch-inf path-inf))
- ((= path-inf max-stretch-inf)
- (setq max-stretch-value (max path-value max-stretch-value)))))))
- (values max-stretch-value
- max-stretch-inf)))
-
- (defun find-path-vertical-stretch (contact top-level-form &optional to-links-p)
- (if (eq contact top-level-form)
- (values 0 0)
- (let ((max-stretch-value 0)
- (max-stretch-inf 0))
- (dolist (link (contact-constraint contact :vertical-links))
- ;; Find the values for a given path, then add in the values for the link.
- (when (eq contact (if to-links-p (link-to link) (link-from link)))
- (multiple-value-bind (path-value path-inf)
- (find-path-vertical-stretch (if to-links-p (link-from link) (link-to link))
- top-level-form
- to-links-p)
- (if (eq (link-maximum link) :infinite)
- (incf path-inf)
- (incf path-value (- (link-maximum link)
- (link-length link))))
- ;; Maximise the number of :infinites, or the numerical value if :infinites
- ;; are equal.
- (cond ((> path-inf max-stretch-inf)
- (setq max-stretch-value path-value
- max-stretch-inf path-inf))
- ((= path-inf max-stretch-inf)
- (setq max-stretch-value (max path-value max-stretch-value)))))))
- ;; Add in the values for the contact.
- (if (eq (form-max-height contact) :infinite)
- (setq max-stretch-inf (1+ max-stretch-inf))
- (setq max-stretch-value (+ (- (form-max-height contact)
- (contact-height contact))
- max-stretch-value)))
- (values max-stretch-value
- max-stretch-inf))))
-
- ;; Shrink is defined as the difference between the current size and the minimum
- ;; size. These functions find the minimum shrink across the link graph.
- (defun find-form-horizontal-shrink (form)
- (dolist (contact (composite-children form))
- (setf (form-tick contact) nil))
- (let ((max-shrink 0))
- (dolist (link (form-horizontal-links form))
- (when (eq form (link-from link))
- (let* ((next-contact (link-to link))
- (path-value (+ (- (link-length link)
- (link-minimum link))
- (find-path-horizontal-shrink next-contact form))))
- (setq max-shrink (max max-shrink path-value))
- (setf (form-tick next-contact) t))))
- (dolist (link (form-horizontal-links form))
- (when (and (eq form (link-to link))
- (null (form-tick (link-from link))))
- (let* ((next-contact (link-from link))
- (path-value (+ (- (link-length link)
- (link-minimum link))
- (find-path-horizontal-shrink next-contact form t))))
- (setq max-shrink (max max-shrink path-value)))))
- max-shrink))
-
- (defun find-path-horizontal-shrink (contact top-level-form &optional to-links-p)
- (if (eq contact top-level-form)
- 0
- (+ (- (contact-width contact)
- (form-min-width contact))
- (let ((max-shrink 0))
- (dolist (link (contact-constraint contact :horizontal-links)
- max-shrink)
- (when (eq contact (if to-links-p (link-to link) (link-from link)))
- (let* ((next-contact (if to-links-p (link-from link) (link-to link)))
- (path-value (+ (- (link-length link)
- (link-minimum link))
- (find-path-horizontal-shrink next-contact top-level-form to-links-p))))
- (setq max-shrink (max max-shrink path-value)))))))))
-
- (defun find-form-vertical-shrink (form)
- (dolist (contact (composite-children form))
- (setf (form-tick contact) nil))
- (let ((max-shrink 0))
- (dolist (link (form-vertical-links form))
- (when (eq form (link-from link))
- (let* ((next-contact (link-to link))
- (path-value (+ (- (link-length link)
- (link-minimum link))
- (find-path-vertical-shrink next-contact form))))
- (setq max-shrink (max max-shrink path-value))
- (setf (form-tick next-contact) t))))
- (dolist (link (form-vertical-links form))
- (when (and (eq form (link-to link))
- (null (form-tick (link-from link))))
- (let* ((next-contact (link-from link))
- (path-value (+ (- (link-length link)
- (link-minimum link))
- (find-path-vertical-shrink next-contact form t))))
- (setq max-shrink (max max-shrink path-value)))))
- max-shrink))
-
- (defun find-path-vertical-shrink (contact top-level-form &optional to-links-p)
- (if (eq contact top-level-form)
- 0
- (+ (- (contact-height contact)
- (form-min-height contact))
- (let ((max-shrink 0))
- (dolist (link (contact-constraint contact :vertical-links)
- max-shrink)
- (when (eq contact (if to-links-p (link-to link) (link-from link)))
- (let* ((next-contact (if to-links-p (link-from link) (link-to link)))
- (path-value (+ (- (link-length link)
- (link-minimum link))
- (find-path-vertical-shrink next-contact top-level-form to-links-p))))
- (setq max-shrink (max max-shrink path-value)))))))))
-
- ;; This function-pair is like find-form-ideal-width except that it uses
- ;; contact-tentative-width instead of contact-width and link-tentative-length
- ;; instead of link-length. It's used in manage-geometry to determine if
- ;; the desired changes will cause a change in the Form's size.
- (defun find-form-tentative-width (form)
- (dolist (contact (composite-children form))
- (setf (form-tick contact) nil))
- (let ((max-width 0))
- (dolist (link (form-horizontal-links form))
- (when (eq form (link-from link))
- ;; Path-value is the length of the link, corrected according to where
- ;; it attaches to the next contact (ie, the distance to the left edge
- ;; of the next contact), plus the value of the maximum path starting
- ;; at that contact.
- (let ((path-value (+ (link-tentative-length link)
- (link-tentative-horizontal-attach-to-correction link)
- (find-path-tentative-width (link-to link) form))))
- (when (eq (link-attach-from link) :center)
- (setq path-value (* 2 path-value)))
- (setq max-width (max max-width path-value))
- (setf (form-tick (link-to link)) t))))
- (dolist (link (form-horizontal-links form))
- (when (and (eq form (link-to link))
- (null (form-tick (link-from link))))
- ;; Path-value is the length of the link, corrected according to where
- ;; it attaches to the next contact (ie, the distance to the left edge
- ;; of the next contact), plus the value of the maximum path starting
- ;; at that contact.
- (let ((path-value (+ (link-tentative-length link)
- (link-tentative-horizontal-attach-from-correction link)
- (find-path-tentative-width (link-from link) form t))))
- (when (eq (link-attach-to link) :center)
- (setq path-value (* 2 path-value)))
- (setq max-width (max max-width path-value)))))
- max-width))
-
- (defun find-path-tentative-width (contact top-level-form &optional to-links-p)
- (if (eq contact top-level-form)
- 0 ; Back at the parent Form, end of path.
- (+ (contact-tentative-width contact)
- (contact-border-width contact)
- (contact-border-width contact)
- (let ((max-width 0))
- (dolist (link (contact-constraint contact :horizontal-links)
- max-width)
- (when (eq contact (if to-links-p (link-to link) (link-from link)))
- (let* ((next-contact (if to-links-p (link-from link) (link-to link)))
- (path-value (+ (if to-links-p
- (link-tentative-horizontal-attach-to-correction link)
- (link-tentative-horizontal-attach-from-correction link))
- (link-tentative-length link)
- ;; Don't compensate when attaching to form.
- (if (eq next-contact top-level-form)
- 0
- (if to-links-p
- (link-tentative-horizontal-attach-from-correction link)
- (link-tentative-horizontal-attach-to-correction link)))
- (find-path-tentative-width next-contact top-level-form to-links-p))))
- (setq max-width (max max-width path-value)))))))))
-
- (defun link-tentative-horizontal-attach-to-correction (link)
- (let ((next-contact (link-to link)))
- (ecase (link-attach-to link)
- (:left 0)
- (:center (- (+ (round (contact-tentative-width next-contact) 2)
- (contact-border-width next-contact))))
- (:right (- (+ (contact-tentative-width next-contact)
- (contact-border-width next-contact)
- (contact-border-width next-contact)))))))
-
- (defun link-tentative-horizontal-attach-from-correction (link)
- (let ((contact (link-from link)))
- (ecase (link-attach-from link)
- (:left (- (+ (contact-tentative-width contact)
- (contact-border-width contact)
- (contact-border-width contact))))
- (:center (- (+ (round (contact-tentative-width contact) 2)
- (contact-border-width contact))))
- (:right 0))))
-
- ;; This function-pair is like find-form-ideal-height except that it uses
- ;; tentative heights, etc, like find-form-tentative-width.
- (defun find-form-tentative-height (form)
- (dolist (contact (composite-children form))
- (setf (form-tick contact) nil))
- (let ((max-height 0))
- (dolist (link (form-vertical-links form))
- (when (eq form (link-from link))
- ;; Path-value is the length of the link, corrected according to where
- ;; it attaches to the next contact, plus the value of the maximum path
- ;; starting at that contact.
- (let ((path-value (+ (link-tentative-length link)
- (link-tentative-vertical-attach-to-correction link)
- (find-path-tentative-height (link-to link) form))))
- (when (eq (link-attach-from link) :center)
- (setq path-value (* 2 path-value)))
- (setq max-height (max max-height path-value))
- (setf (form-tick (link-to link)) t))))
- (dolist (link (form-vertical-links form))
- (when (and (eq form (link-to link))
- (null (form-tick (link-from link))))
- ;; Path-value is the length of the link, corrected according to where
- ;; it attaches to the next contact, plus the value of the maximum path
- ;; starting at that contact.
- (let ((path-value (+ (link-tentative-length link)
- (link-tentative-vertical-attach-from-correction link)
- (find-path-tentative-height (link-from link) form t))))
- (when (eq (link-attach-to link) :center)
- (setq path-value (* 2 path-value)))
- (setq max-height (max max-height path-value)))))
- max-height))
-
- (defun find-path-tentative-height (contact top-level-form &optional to-links-p)
- (if (eq contact top-level-form)
- 0
- (+ (contact-tentative-height contact)
- (contact-border-width contact)
- (contact-border-width contact)
- (let ((max-height 0))
- (dolist (link (contact-constraint contact :vertical-links)
- max-height)
- (when (eq contact (if to-links-p (link-to link) (link-from link)))
- (let* ((next-contact (if to-links-p (link-from link) (link-to link)))
- (path-value (+ (if to-links-p
- (link-tentative-vertical-attach-to-correction link)
- (link-tentative-vertical-attach-from-correction link))
- (link-tentative-length link)
- ;; Don't compensate when attaching to form.
- (if (eq next-contact top-level-form)
- 0
- (if to-links-p
- (link-tentative-vertical-attach-from-correction link)
- (link-tentative-vertical-attach-to-correction link)))
- (find-path-tentative-height next-contact top-level-form to-links-p))))
- (setq max-height (max max-height path-value)))))))))
-
- (defun link-tentative-vertical-attach-to-correction (link)
- (let ((next-contact (link-to link)))
- (ecase (link-attach-to link)
- (:top 0)
- (:center (- (+ (round (contact-tentative-height next-contact) 2)
- (contact-border-width next-contact))))
- (:bottom (- (+ (contact-tentative-height next-contact)
- (contact-border-width next-contact)
- (contact-border-width next-contact)))))))
-
- (defun link-tentative-vertical-attach-from-correction (link)
- (let ((contact (link-from link)))
- (ecase (link-attach-from link)
- (:top (- (+ (contact-tentative-height contact)
- (contact-border-width contact)
- (contact-border-width contact))))
- (:center (- (+ (round (contact-tentative-height contact) 2)
- (contact-border-width contact))))
- (:bottom 0))))
-
-
- ;;;
- ;;; Abstractions of length comparisons that honor the :infinite length.
-
- (defun length<= (length &rest lengths)
- (do ((a length c)
- (b lengths (cdr b))
- (c))
- ((null b) t)
- (setq c (car b))
- (if (and (not (eq c :infinite))
- (or (eq a :infinite)
- (> a c)))
- (return nil))))
-
- (defun length> (length-1 length-2)
- (cond ((eq length-2 :infinite)
- nil)
- ((eq length-1 :infinite)
- t)
- ((> length-1 length-2))))
-
- (defun length-min (length-1 length-2)
- (cond ((eq length-1 :infinite)
- length-2)
- ((eq length-2 :infinite)
- length-1)
- ((< length-1 length-2)
- length-1)
- (:else
- length-2)))
-
-
- ;;;
- ;;; Form characteristic methods. Below are preferred-size, change-layout,
- ;;; resize, and manage-geometry. These guys provide the interface that
- ;;; uses the functions above.
-
- ;; If a what-if size is supplied, try it and return the tentative width and height.
- ;; If not, just return the current "ideal" width and height.
- ;; +++ Not thoroughly tested.
- (defmethod preferred-size ((form form) &key width height border-width)
- (with-slots ((form-width width)
- (form-height height))
- form
- (let ((pref-width (find-form-ideal-width form))
- (pref-height (find-form-ideal-height form)))
- (if (or (and width (/= width form-width))
- (and height (/= height form-height)))
- ;; Wants to try a new width and/or height.
- (let ((new-form-width (or width form-width))
- (new-form-height (or height form-height)))
- ;; Try the new placement, changing only tentative values.
- (clear-tentative-values form)
- (place-and-size-children-internal
- form
- (- new-form-width (max pref-width form-width))
- (- new-form-height (max pref-height form-height)))
- (values (find-form-tentative-width form)
- (find-form-tentative-height form)
- (or border-width (contact-border-width form))))
- (values pref-width
- pref-height
- (or border-width (contact-border-width form)))))))
-
-
- ;; Several consistency checks happen in change-layout: (1) Look for circular
- ;; links, ie, cases where links among contacts form a loop. (2) Look for cases
- ;; where children haven't been given a size and ensure that they are sized
- ;; initially such that their constraints are satisfied -- for example, the Form
- ;; has a specified size and the children should be sized to fit it and the links.
- ;; (3) Look for links where the length does not equal the distance between the
- ;; endpoints -- this is an inconsistency that should either signal an error or
- ;; cause some link-stretching. (2) and (3) happen somewhat together in
- ;; adjust-sizes-to-fit, called from place-and-size-children.
-
- (defmethod change-layout ((form form) &optional newly-managed)
- (declare (type (or null contact) newly-managed))
-
- ;; Convert any initarg link-specs into links.
- (resolve-initial-links form)
-
- ;; Check for and handle a single child being unmapped...
- (when (and newly-managed
- (eq (contact-state newly-managed) :withdrawn))
- )
-
- ;; If there are any circular link paths, error here.
- (check-for-circular-links form)
-
- ;; Ensure that children have sizes that fit within their size constraints.
- (set-initial-child-sizes form)
-
- ;; Set the Form's initial size, when necessary, then place and adjust the children.
- (multiple-value-bind (pref-width pref-height)
- (preferred-size form)
- (cond ((and (not (realized-p form))
- (or (zerop (contact-height form))
- (zerop (contact-width form))))
- ;; Form's dimensions are uninitialised: Take the preferred size of the whole,
- ;; then place the children where they want to be. Supply the difference
- ;; between the preferred size and the actual size in case the Form didn't
- ;; get the size it requested.
- (change-geometry form :width pref-width :height pref-height :accept-p t)
- (place-and-size-children form
- (- (contact-width form) pref-width)
- (- (contact-height form) pref-height)
- t))
- ((and (not (zerop (contact-height form)))
- (not (zerop (contact-width form)))
- (or (/= (contact-width form) pref-width)
- (/= (contact-height form) pref-height)))
- ;; Form has a size, and it's different than the preferred size of the whole.
- ;; Resize the children to match.
- (place-and-size-children form
- (- (contact-width form) pref-width)
- (- (contact-height form) pref-height)
- t))
- (:else
- ;; Either the Form has the same size as the children want, or some other case.
- ;; Just place the children.
- (place-and-size-children form nil nil t)))))
-
- ;; Check for links set up as initargs and not yet resolved. Instead of a link
- ;; object, the link will be a list, an argument list to make-horizontal-link or
- ;; make-vertical-link, with contact-names instead of contacts. Find the contacts
- ;; and make the links.
- (defun resolve-initial-links (form)
- (declare (type form form))
- (with-slots (horizontal-links vertical-links children name) form
- (check-type horizontal-links list)
- (check-type vertical-links list)
-
- ;; Note that links can't be defstructs of :type :list or this test won't work.
- (let ((link-specs (remove-if-not #'listp horizontal-links)))
- (setq horizontal-links (nset-difference horizontal-links link-specs))
- (dolist (spec link-specs)
- ;; A spec instead of a link, delete it and do the make-link. We make one pass
- ;; through all the links this way because it's possible to specify link-specs
- ;; in the initargs and do make-link later and have both coexist until realisation.
- (let* ((from-name (getf spec :from))
- (to-name (getf spec :to))
- (from (if (eq name from-name)
- form
- (find from-name children :key #'contact-name)))
- (to (if (eq name to-name)
- form
- (find to-name children :key #'contact-name))))
- (if (or (null from) (null to))
- (error "Link spec referred to nonexistent contact: ~S" spec)
- (apply #'make-horizontal-link :from from :to to spec)))))
-
- ;; Note that links can't be defstructs of :type :list or this test won't work.
- (let ((link-specs (remove-if-not #'listp vertical-links)))
- (setq vertical-links (nset-difference vertical-links link-specs))
- (dolist (spec link-specs)
- ;; A spec instead of a link, delete it and do the make-link. We make one pass
- ;; through all the links this way because it's possible to specify link-specs
- ;; in the initargs and do make-link later and have both coexist until realisation.
- (let* ((from-name (getf spec :from))
- (to-name (getf spec :to))
- (from (if (eq name from-name)
- form
- (find from-name children :key #'contact-name)))
- (to (if (eq name to-name)
- form
- (find to-name children :key #'contact-name))))
- (if (or (null from) (null to))
- (error "Link spec referred to nonexistent contact: ~S" spec)
- (apply #'make-vertical-link :from from :to to spec))))))
-
- ;; If there are subgraphs that have no ultimate link connection to the Form,
- ;; we add implicit 0-to-infinite links, so they'll play a part in the sizing
- ;; algorithm.
- (add-implicit-links-if-needed form))
-
- (defun add-implicit-links-if-needed (form)
- (declare (type form form))
- (labels ((mark-link-path (contact top-level-form link-type)
- (unless (or (eq contact top-level-form)
- (form-tick contact))
- (setf (form-tick contact) t)
- (dolist (link (contact-constraint contact link-type))
- (mark-link-path (link-from link) top-level-form link-type)
- (mark-link-path (link-to link) top-level-form link-type)))))
-
- (with-slots (horizontal-links vertical-links children) form
- ;; The first thing we do is flush any existing implicit links,
- ;; in case change-layout was called because of a new child with
- ;; links we don't want to interfere with.
- (dolist (child children)
- (dolist (link (contact-constraint child :horizontal-links))
- (when (link-implicit-p link)
- (destroy link)))
- (dolist (link (contact-constraint child :vertical-links))
- (when (link-implicit-p link)
- (destroy link))))
-
- ;; Then we walk the link graph, marking children as we go.
- ;; We add implicit links to the unmarked children that don't have links
- ;; in a given direction.
- (dolist (child children)
- (setf (form-tick child) nil))
- (dolist (link horizontal-links)
- (mark-link-path (link-from link) form :horizontal-links)
- (mark-link-path (link-to link) form :horizontal-links))
- (dolist (child children)
- (unless (form-tick child)
- (let ((to-p nil)
- (from-p nil))
- (dolist (link (contact-constraint child :horizontal-links))
- (cond ((eq child (link-from link))
- (setq from-p t))
- ((eq child (link-to link))
- (setq to-p t))))
- (when (null from-p) ; Add a to-link.
- (let ((new-link (make-horizontal-link :from child :to form :attach-to :right)))
- (setf (link-implicit-p new-link) t)))
- (when (null to-p) ; Add a from-link.
- (let ((new-link (make-horizontal-link :from form :to child :attach-from :left)))
- (setf (link-implicit-p new-link) t))))))
-
- ;; Again for the verticals.
- (dolist (child children)
- (setf (form-tick child) nil))
- (dolist (link vertical-links)
- (mark-link-path (link-from link) form :vertical-links)
- (mark-link-path (link-to link) form :vertical-links))
- (dolist (child children)
- (unless (form-tick child)
- (let ((to-p nil)
- (from-p nil))
- (dolist (link (contact-constraint child :vertical-links))
- (cond ((eq child (link-from link))
- (setq from-p t))
- ((eq child (link-to link))
- (setq to-p t))))
- (when (null from-p) ; Add a to-link.
- (let ((new-link (make-vertical-link :from child :to form :attach-to :bottom)))
- (setf (link-implicit-p new-link) t)))
- (when (null to-p) ; Add a from-link.
- (let ((new-link (make-vertical-link :from form :to child :attach-from :top)))
- (setf (link-implicit-p new-link) t)))))))))
-
-
- ;; The circularity check is a simple traversal of first the horizontal
- ;; links and then the vertical links. For each, we travel to all the contacts
- ;; in depth-first order, marking contacts as we see them. If we see a marked
- ;; contact, we've found a circle and error. We undo the marks as we backtrack,
- ;; to allow the possibility of multiple non-circular paths to the same contact.
- (defun check-for-circular-links (form)
- (labels ((check-for-circular-horizontal-links (form)
- ;; Form-tick is used to mark contacts as they are visited.
- (dolist (contact (composite-children form))
- (setf (form-tick contact) nil))
- (dolist (link (form-horizontal-links form))
- (when (eq form (link-from link))
- (check-for-circular-horizontal-links-path (link-to link) form))))
-
- (check-for-circular-horizontal-links-path (contact top-level-form)
- (unless (eq contact top-level-form) ; Back at the parent Form, end of path.
- (when (form-tick contact)
- (error "Circular horizontal-link path found at ~S." contact))
- (setf (form-tick contact) t)
- (dolist (link (contact-constraint contact :horizontal-links))
- (when (eq contact (link-from link))
- (check-for-circular-horizontal-links-path (link-to link) top-level-form)))
- (setf (form-tick contact) nil)))
-
- (check-for-circular-vertical-links (form)
- ;; Form-tick is used to mark contacts as they are visited.
- (dolist (contact (composite-children form))
- (setf (form-tick contact) nil))
- (dolist (link (form-vertical-links form))
- (when (eq form (link-from link))
- (check-for-circular-vertical-links-path (link-to link) form))))
-
- (check-for-circular-vertical-links-path (contact top-level-form)
- (unless (eq contact top-level-form) ; Back at the parent Form, end of path.
- (when (form-tick contact)
- (error "Circular vertical-link path found at ~S." contact))
- (setf (form-tick contact) t)
- (dolist (link (contact-constraint contact :vertical-links))
- (when (eq contact (link-from link))
- (check-for-circular-vertical-links-path (link-to link) top-level-form)))
- (setf (form-tick contact) nil))))
-
- (check-for-circular-horizontal-links form)
- (check-for-circular-vertical-links form)))
-
- ;; Set initial sizes. Ensure that the children (a) have widths and heights and
- ;; (b) those widths and heights are within their constraints.
- ;;
- ;; Case A: child has size -- if within constraints, fine, else set it
- ;; to the minimum or maximum (whichever is nearer). Case B: child doesn't
- ;; have a size (ie, dimensions of zero) -- set size to min if present, or
- ;; preferred-size if not, because form-min-width and form-min-height will
- ;; default to the current size if not specified.
- (defun set-initial-child-sizes (form)
- (with-slots (children) form
- (dolist (child children)
- (if (or (zerop (contact-width child))
- (zerop (contact-height child)))
- ;; No size given, take the minimum if there is one and the preferred
- ;; if there isn't.
- (multiple-value-bind (pref-width pref-height)
- (preferred-size child)
- (resize child
- (if (zerop (contact-width child))
- (if (zerop (form-min-width child))
- pref-width
- (form-min-width child))
- (contact-width child))
- (if (zerop (contact-height child))
- (if (zerop (form-min-height child))
- pref-height
- (form-min-height child))
- (contact-height child))
- (contact-border-width child)))
- ;; Size given, resize the child if it exceeds its constraints.
- (when (or (not (length<= (form-min-width child)
- (contact-width child)
- (form-max-width child)))
- (not (length<= (form-min-height child)
- (contact-height child)
- (form-max-height child))))
- (resize child
- (max (length-min (contact-width child) ; +++ Should this be preferred or current?
- (form-max-width child))
- (form-min-width child))
- (max (length-min (contact-height child)
- (form-max-height child))
- (form-min-height child))
- (contact-border-width child))))
-
- ;; If there isn't a minimum size specified, make it be the initial size
- ;; so later resizes won't forget it.
- (when (null (contact-constraint child :min-width))
- (setf (form-min-width child) (contact-width child)))
- (when (null (contact-constraint child :min-height))
- (setf (form-min-height child) (contact-height child))))))
-
- ;; Idea borrowed from property-sheet. Catch the preferred-size and the current size,
- ;; go do the resize on the Form, then adjust the children according to the difference
- ;; between the new size and the old. Taking the larger of preferred-size and initial
- ;; size ensures that the children don't try to grow or move until the Form is larger
- ;; than the minimum as set by the children's constraints.
- (defmethod resize :around ((form form) width height border-width)
- (let ((initial-width (contact-width form))
- (initial-height (contact-height form)))
- (multiple-value-bind (pw ph)
- (preferred-size form)
- (let ((resized-p (call-next-method)))
- (unless (or (zerop initial-width) ; To avoid startup glitches.
- (zerop initial-height)
- (getf (window-plist form) 'in-manage-geometry))
- (place-and-size-children form
- (- width (max pw initial-width))
- (- height (max ph initial-height))))
- resized-p))))
-
-
- ;; The algorithm: If the requested size change can happen entirely
- ;; without disturbing the other children or the Form (ie, a shrink within
- ;; the limits of the link stretchability, or a grow that doesn't push the
- ;; neighbors aside), then do it and adjust the links accordingly. If not,
- ;; see if a position change, still within the limits of the links, will
- ;; allow the size change, and return that as a compromise geometry if it
- ;; works. If neither idea works, treat the situation as an initial change-layout
- ;; with new initial conditions (two subcases here, depending on whether or
- ;; not the Form has to change size to accommodate the change).
- ;;
- ;; More of the algorithm: When leading up to a change-layout, use a variant
- ;; of find-ideal-form-width, etc, that looks at the tentative size, flushing
- ;; all the tentative sizes except the changing child's, and use that size to
- ;; determine whether or not the Form needs to change size. Use that answer
- ;; to either call change-geometry upwards or not, then do change-layout just
- ;; before returning if we're going to approve it. If we're not going to
- ;; approve, return NIL and maybe whatever size we could handle (or the original
- ;; size, or nothing at all).
- (defmethod manage-geometry ((form form) (child contact)
- x y width height border-width &key)
- (with-slots ((child-width width)
- (child-height height)
- (child-border-width border-width)
- (child-x x)
- (child-y y))
- child
- (let* ((approved-p t)
- (total-width (+ child-width child-border-width child-border-width))
- (total-height (+ child-height child-border-width child-border-width))
- (requested-width (or width child-width))
- (requested-height (or height child-height))
- (requested-border-width (or border-width child-border-width))
- (requested-x (or x child-x))
- (requested-y (or y child-y))
- (new-total-width (+ requested-width requested-border-width requested-border-width))
- (new-total-height (+ requested-height requested-border-width requested-border-width)))
-
- ;; Check if requested size change fits within size constraints. If not,
- ;; disapprove and limit it to within them.
- (when (or (not (length<= (form-min-width child) requested-width (form-max-width child)))
- (not (length<= (form-min-height child) requested-height (form-max-height child))))
- (setq approved-p nil) ; Tried to exceed size constraints.
- (setq requested-width (max (length-min requested-width
- (form-max-width child))
- (form-min-width child))
- requested-height (max (length-min requested-height
- (form-max-height child))
- (form-min-height child))))
-
- ;; Check if the change can be done without affecting any other children.
- ;; If so, allow the change and modify the links accordingly; if not,
- ;; go back to square one and do change-layout.
- (let ((delta-left (- requested-x child-x)) ; Calculate changes in attach-points.
- (delta-top (- requested-y child-y))
- (delta-right (- (+ requested-x new-total-width)
- (+ child-x total-width)))
- (delta-bottom (- (+ requested-y new-total-height)
- (+ child-y total-height)))
- (delta-h-center (- (round (+ requested-x new-total-width) 2)
- (round (+ child-x total-width) 2)))
- (delta-v-center (- (round (+ requested-y new-total-height) 2)
- (round (+ child-y total-height) 2)))
- (left-excess 0)
- (right-excess 0)
- (top-excess 0)
- (bottom-excess 0))
-
- (labels ((punt ()
- ;; If we give up completely, disapprove and return the original
- ;; geometry as the compromise.
- (setq approved-p nil
- requested-height child-height
- requested-width child-width
- requested-x child-x
- requested-y child-y
- requested-border-width child-border-width))
-
- ;; These next two functions either add or subtract the deltas from the link-lengths
- ;; based on the direction of the link. There's not a lot of theory behind it,
- ;; but it has to do with the meaning of a positive length.
- (tentative-link-length-horizontal (link)
- (if (eq child (link-to link))
- (+ (link-length link)
- (ecase (link-attach-to link)
- (:left delta-left)
- (:center delta-h-center)
- (:right delta-right)))
- (- (link-length link)
- (ecase (link-attach-from link)
- (:left delta-left)
- (:center delta-h-center)
- (:right delta-right)))))
-
- (tentative-link-length-vertical (link)
- (if (eq child (link-to link))
- (+ (link-length link)
- (ecase (link-attach-to link)
- (:top delta-top)
- (:center delta-v-center)
- (:bottom delta-bottom)))
- (- (link-length link)
- (ecase (link-attach-from link)
- (:top delta-top)
- (:center delta-v-center)
- (:bottom delta-bottom)))))
-
- (manage-geometry-hard-case ()
- ;; If all else fails, come here and do most of what change-layout does,
- ;; using the child's requested geometry, and see if it works out.
- (clear-tentative-values form)
- (setf (contact-tentative-width child) requested-width)
- (setf (contact-tentative-height child) requested-height)
- (setf (contact-tentative-x child) requested-x)
- (setf (contact-tentative-y child) requested-y)
- (let ((new-form-width (find-form-tentative-width form))
- (new-form-height (find-form-tentative-height form)))
- (unless (and (= new-form-width (contact-width form))
- (= new-form-height (contact-height form)))
- ;; Form has to change size. The first thing to do is to pretend to
- ;; change it, using form-projected-width and form-projected-height,
- ;; and see if any other child's links will be violated (they may be
- ;; changed, and/or the children's sizes may, but they're not allowed
- ;; to exceed their constraints).
- ;;
- ;; If none are, go ahead and try the resize. If there's a violation,
- ;; punt, because we've tried to change the whole layout to accomodate
- ;; the change and still can't satisfy the constraints.
- (setf (form-projected-height form) new-form-height)
- (setf (form-projected-width form) new-form-width)
- (place-and-size-children-internal form nil nil) ; Do the tentative placements.
- (let ((link-change-okay? (null (adjust-sizes-to-fit form))))
- (cond ((or (/= (contact-tentative-width child) requested-width)
- (/= (contact-tentative-height child) requested-height))
- ;; The attempted layout would change the size of the child, so
- ;; try again with the changed size and return that result (with
- ;; NIL for approved-p because the changed size means disapproval).
- (multiple-value-setq (approved-p requested-x requested-y
- requested-width requested-height requested-border-width)
- (manage-geometry form child
- (contact-tentative-x child)
- (contact-tentative-y child)
- (contact-tentative-width child)
- (contact-tentative-height child)
- requested-border-width))
- (setq approved-p nil))
- (link-change-okay?
- ;; No links violated, go try to change size. The "in-manage-geometry" flag
- ;; will prevent the resize from calling place-and-size-children, which
- ;; would scramble our efforts and flush the tentative values. It's a
- ;; flag instead of a special variable because it's specific to one window.
- ;;
- ;; +++ Note that we only care about the first value of change-geometry, because
- ;; at this point we aren't trying to handle partial resizes. When/if we do,
- ;; this'll change to a multiple-value-bind.
- (let ((form-approved-p
- (unwind-protect
- (progn
- (setf (getf (window-plist form) 'in-manage-geometry) t)
- ; (change-geometry form :width new-form-width :height new-form-height)
- (manage-geometry (contact-parent form) form nil nil
- new-form-width new-form-height nil)
- )
- (setf (getf (window-plist form) 'in-manage-geometry) nil))))
- (when (not form-approved-p)
- ;; New Form size not approved.
- ;; +++ A smoother solution would let the child have part of its request,
- ;; but for the moment we'll just refuse it completely.
- (punt))))
- (:else
- ;; Can't handle the link change, so, for now, punt. Disapprove the change
- ;; and return the original size as the compromise.
- (punt)))))
- ;; If, after all that, we approve, do the changes now.
- (when approved-p
- (cond ((form-projected-height form)
- ;; Form has to change size, so include it in the approval function.
- (setq approved-p #'(lambda (form)
- (unwind-protect
- (progn
- (setf (getf (window-plist form) 'in-manage-geometry) t)
- (change-geometry form
- :width new-form-width
- :height new-form-height))
- (setf (getf (window-plist form) 'in-manage-geometry) nil))
- (really-change-the-children form))))
- (:else
- ;; Non-NIL only when Form changed size, thus when place-and-size
- ;; has already been done.
- (setf (form-projected-height form) nil)
- (setf (form-projected-width form) nil)
- (place-and-size-children-internal form nil nil)
- (setq approved-p #'really-change-the-children))))
- (values approved-p
- requested-x
- requested-y
- requested-width
- requested-height
- requested-border-width))))
-
- ;; Calculate the amount that the new geometry causes the link constraints
- ;; to be exceeded. If it's all zero, no link's constraints are exceeded
- ;; and we can proceed without disturbing anyone.
- (dolist (link (contact-constraint child :horizontal-links))
- (let ((tentative-link-length (tentative-link-length-horizontal link)))
- (if (or (and (eq child (link-to link))
- (> tentative-link-length 0))
- (and (eq child (link-from link))
- (< tentative-link-length 0)))
- ;; Note the implicit assumption that we won't simultaneously exceed both
- ;; the minimum and the maximum in a given direction.
- (cond ((< tentative-link-length (link-minimum link))
- (setq left-excess (min left-excess
- (- tentative-link-length (link-minimum link)))))
- ((length> tentative-link-length (link-maximum link))
- (setq left-excess (max left-excess
- (- tentative-link-length (link-maximum link))))))
- (cond ((< tentative-link-length (link-minimum link))
- (setq right-excess (min right-excess
- (- tentative-link-length (link-minimum link)))))
- ((length> tentative-link-length (link-maximum link))
- (setq right-excess (max right-excess
- (- tentative-link-length (link-maximum link)))))))))
- (dolist (link (contact-constraint child :vertical-links))
- (let ((tentative-link-length (tentative-link-length-vertical link)))
- (if (or (and (eq child (link-to link))
- (> tentative-link-length 0))
- (and (eq child (link-from link))
- (< tentative-link-length 0)))
- ;; Note the implicit assumption that we won't simultaneously exceed both
- ;; the minimum and the maximum in a given direction.
- (cond ((< tentative-link-length (link-minimum link))
- (setq top-excess (min top-excess
- (- tentative-link-length (link-minimum link)))))
- ((length> tentative-link-length (link-maximum link))
- (setq top-excess (max top-excess
- (- tentative-link-length (link-maximum link))))))
- (cond ((< tentative-link-length (link-minimum link))
- (setq bottom-excess (min bottom-excess
- (- tentative-link-length (link-minimum link)))))
- ((length> tentative-link-length (link-maximum link))
- (setq bottom-excess (max bottom-excess
- (- tentative-link-length (link-maximum link)))))))))
-
- (cond ((and (zerop left-excess)
- (zerop right-excess)
- (zerop top-excess)
- (zerop bottom-excess))
- ;; Okay, the proposed size and placement won't strain any links.
- ;; Approve the change without affecting anyone else (bearing in mind
- ;; that the size may have been constrained above).
- (when approved-p
- ;; Since we're approving, set up the child for the geometry change.
- (clear-tentative-values form)
- (setf (contact-tentative-width child) requested-width)
- (setf (contact-tentative-height child) requested-height)
- (setf (contact-tentative-x child) requested-x)
- (setf (contact-tentative-y child) requested-y)
-
- ;; We're about to approve fully. Modify the links to fit the new
- ;; size and placement of the child, basically the same loops as above
- ;; but for effect rather than verification.
- (dolist (link (contact-constraint child :horizontal-links))
- (setf (link-tentative-length link) (tentative-link-length-horizontal link)))
- (dolist (link (contact-constraint child :vertical-links))
- (setf (link-tentative-length link) (tentative-link-length-vertical link)))
-
- ;; We're approving fully, so actually do the change.
- (really-change-the-children form))
-
- ;; All done, return the indicated values.
- (values approved-p
- requested-x
- requested-y
- requested-width
- requested-height
- requested-border-width))
-
- ((and (or (zerop left-excess)
- (zerop right-excess)
- (and (minusp left-excess)
- (plusp right-excess))
- (and (plusp left-excess)
- (minusp right-excess)))
- (or (zerop top-excess)
- (zerop bottom-excess)
- (and (minusp top-excess)
- (plusp bottom-excess))
- (and (plusp top-excess)
- (minusp bottom-excess))))
- ;; Okay, we exceed one side in one direction and the other side either not
- ;; at all or in the other direction. Try moving enough to handle the excess,
- ;; and if we don't violate any of the child's links with the new position,
- ;; disapprove but return the new position as a compromise.
- (let ((x-change (if (> (abs left-excess)
- (abs right-excess))
- left-excess
- right-excess))
- (y-change (if (> (abs top-excess)
- (abs bottom-excess))
- top-excess
- bottom-excess)))
- (setq delta-left (if (> left-excess 0)
- (- delta-left x-change)
- (+ delta-left x-change))
- delta-right (if (> left-excess 0)
- (- delta-right x-change)
- (+ delta-right x-change))
- delta-h-center (if (> left-excess 0)
- (- delta-h-center x-change)
- (+ delta-h-center x-change))
- delta-top (if (> top-excess 0)
- (- delta-top y-change)
- (+ delta-top y-change))
- delta-bottom (if (> top-excess 0)
- (- delta-bottom y-change)
- (+ delta-bottom y-change))
- delta-v-center (if (> top-excess 0)
- (- delta-v-center y-change)
- (+ delta-v-center y-change)))
-
- ;; Check if the new deltas cause any link violations. If not, return
- ;; the new position and the requested size as the compromise geometry.
- ;; If so, go to the hard case.
- (if (and (dolist (link (contact-constraint child :horizontal-links)
- t)
- (let ((tentative-link-length (tentative-link-length-horizontal link)))
- ;; Projected link length exceeds limits, return NIL now.
- ;; If all link projections work, the DOLIST will return T.
- (unless (length<= (link-minimum link)
- tentative-link-length
- (link-maximum link))
- (return nil))))
- (dolist (link (contact-constraint child :vertical-links)
- t)
- (let ((tentative-link-length (tentative-link-length-vertical link)))
- ;; Projected link length exceeds limits, return NIL now.
- ;; If all link projections work, the DOLIST will return T.
- (unless (length<= (link-minimum link)
- tentative-link-length
- (link-maximum link))
- (return nil)))))
- (values nil ; Can't approve, because we moved it.
- (if (> left-excess 0)
- (- requested-x x-change)
- (+ requested-x x-change))
- (if (> top-excess 0)
- (- requested-y y-change)
- (+ requested-y y-change))
- requested-width
- requested-height
- requested-border-width)
- (manage-geometry-hard-case))))
-
- (:else
- ;; The "hard" case -- the proposed change would violate one or more of
- ;; the links, so essentially do change-layout again with the requested
- ;; size and position as the new initial conditions of the child.
- (manage-geometry-hard-case))))))))
-
- ;; Checks for links whose endpoints aren't where they should be.
- ;; Go through all the links, checking the actual length against the distance
- ;; between the attach-points. When there's a discrepancy, collect the link
- ;; and the desired length for later use.
- (defun find-disturbed-links (form)
- (nconc (find-horizontal-disturbed-links form)
- (find-vertical-disturbed-links form)))
-
- (defun find-horizontal-disturbed-links (form)
- (let ((changes nil))
- (dolist (link (form-horizontal-links form))
- (when (eq form (link-from link))
- (let ((desired-length (- (contact-tentative-x (link-to link))
- (link-horizontal-attach-to-correction link)
- (ecase (link-attach-from link)
- (:left 0)
- (:center (round (form-projected-width form) 2))
- (:right (form-projected-width form))))))
- (unless (= (link-tentative-length link) desired-length)
- (push (cons link desired-length) changes)))))
- (dolist (child (composite-children form))
- (dolist (link (contact-constraint child :horizontal-links))
- (when (eq child (link-from link))
- (let ((desired-length (if (eq (link-to link) form)
- (abs (+ (contact-tentative-x (link-from link))
- (- (ecase (link-attach-to link)
- (:left 0)
- (:center (round (form-projected-width form) 2))
- (:right (form-projected-width form))))
- (ecase (link-attach-from link)
- (:left 0)
- (:center (+ (round (contact-tentative-width child) 2)
- (contact-border-width child)))
- (:right (+ (contact-tentative-width child)
- (contact-border-width child)
- (contact-border-width child))))))
- (- (contact-tentative-x (link-to link))
- (contact-tentative-x (link-from link))
- (link-horizontal-attach-to-correction link)
- (ecase (link-attach-from link)
- (:left 0)
- (:center (+ (round (contact-tentative-width child) 2)
- (contact-border-width child)))
- (:right (+ (contact-tentative-width child)
- (contact-border-width child)
- (contact-border-width child))))))))
- (unless (= (link-tentative-length link) desired-length)
- (push (cons link desired-length) changes))))))
- changes))
-
- (defun find-vertical-disturbed-links (form)
- (let ((changes nil))
- (dolist (link (form-vertical-links form))
- (when (eq form (link-from link))
- (let ((desired-length (- (contact-tentative-y (link-to link))
- (link-vertical-attach-to-correction link)
- (ecase (link-attach-from link)
- (:top 0)
- (:center (round (form-projected-height form) 2))
- (:bottom (form-projected-height form))))))
- (unless (= (link-tentative-length link) desired-length)
- (push (cons link desired-length) changes)))))
- (dolist (child (composite-children form))
- (dolist (link (contact-constraint child :vertical-links))
- (when (eq child (link-from link))
- (let ((desired-length (if (eq (link-to link) form)
- (abs (+ (contact-tentative-y (link-from link))
- (- (ecase (link-attach-to link)
- (:top 0)
- (:center (round (form-projected-height form) 2))
- (:bottom (form-projected-height form))))
- (ecase (link-attach-from link)
- (:top 0)
- (:center (+ (round (contact-tentative-height child) 2)
- (contact-border-width child)))
- (:bottom (+ (contact-tentative-height child)
- (contact-border-width child)
- (contact-border-width child))))))
- (- (contact-tentative-y (link-to link))
- (contact-tentative-y (link-from link))
- (link-vertical-attach-to-correction link)
- (ecase (link-attach-from link)
- (:top 0)
- (:center (+ (round (contact-tentative-height child) 2)
- (contact-border-width child)))
- (:bottom (+ (contact-tentative-height child)
- (contact-border-width child)
- (contact-border-width child))))))))
- (unless (= (link-tentative-length link) desired-length)
- (push (cons link desired-length) changes))))))
- changes))
-
-
- ;;;
- ;;; Resize-algorithm core functions. The basics of the resize algorithm
- ;;; are contained in the functions below. The algorithm is: When the width
- ;;; of the Form changes, calculate the maximum stretch or shrink across the
- ;;; link graph and divide the width difference accordingly among the children.
- ;;; Do the same with the height. Then, go through the horizontal link graph
- ;;; and move the children to account for changes in the sizes of contacts and
- ;;; links. Do the same with the vertical link graph. That's all. (When a
- ;;; child resizes itself, manage-geometry handles it.)
-
- ;; Function called from change-layout. Follows the algorithm described above, with
- ;; the efficiency hack described with the "tentative" abstraction macros. It's
- ;; broken into "clear," "internal," "adjust," and "really" so manage-geometry can
- ;; use them separately.
- (defun place-and-size-children (form &optional width-difference height-difference adjust-p)
- (clear-tentative-values form)
- (place-and-size-children-internal form width-difference height-difference (not adjust-p))
- (when adjust-p
- (let ((adjustments-needed (adjust-sizes-to-fit form)))
- (when adjustments-needed
- (error "Inconsistent or incomplete layout constraints: ~{~& ~A~}"
- (let ((l nil))
- (dolist (adj adjustments-needed l)
- (let ((link (car adj))
- (length (cdr adj)))
- (push (format nil "~A link from ~A to ~A wants to be ~D long, ~
- but its limits are ~D and ~D."
- (link-orientation link)
- (contact-name (link-from link))
- (contact-name (link-to link))
- length
- (link-minimum link)
- (link-maximum link))
- l))))))))
- (really-change-the-children form))
-
- (defun clear-tentative-values (form)
- ;; Flush the old cached values from the last place-and-resize.
- ;; +++ Would it be appropriate to remf them at the end instead? This
- ;; way at least the consing is limited, but it never goes away.
- (with-slots (children) form
- (dolist (contact children)
- (setf (contact-tentative-width contact) nil)
- (setf (contact-tentative-height contact) nil)
- (setf (contact-tentative-x contact) nil)
- (setf (contact-tentative-y contact) nil)
- (dolist (link (contact-constraint contact :horizontal-links))
- (setf (link-tentative-length link) nil))
- (dolist (link (contact-constraint contact :vertical-links))
- (setf (link-tentative-length link) nil))))
- (setf (form-projected-width form) nil)
- (setf (form-projected-height form) nil))
-
- (defun place-and-size-children-internal (form width-difference height-difference &optional (error-p t))
- ;; Figure out the new sizes and placements, given the changes in Form size.
- (when (and width-difference
- (not (zerop width-difference)))
- (resize-children-horizontal form width-difference))
- (when (and height-difference
- (not (zerop height-difference)))
- (resize-children-vertical form height-difference))
-
- (place-children-from-form-horizontal form error-p)
- (place-children-from-form-vertical form error-p))
-
- (defun really-change-the-children (form)
- ;; The above just set up cached values for x, y, width, and height.
- ;; Now go through the children and adjust where appropriate.
- (with-slots (children) form
- (dolist (contact children)
- (with-state (contact)
- (when (or (/= (contact-tentative-x contact) (contact-x contact))
- (/= (contact-tentative-y contact) (contact-y contact)))
- (move contact
- (contact-tentative-x contact)
- (contact-tentative-y contact)))
- (when (or (/= (contact-tentative-width contact) (contact-width contact))
- (/= (contact-tentative-height contact) (contact-height contact)))
- (resize contact
- (contact-tentative-width contact)
- (contact-tentative-height contact)
- (contact-border-width contact))))
- (dolist (link (contact-constraint contact :horizontal-links))
- (when (/= (link-tentative-length link) (link-length link))
- (setf (slot-value link 'length) (link-tentative-length link))))
- (dolist (link (contact-constraint contact :vertical-links))
- (when (/= (link-tentative-length link) (link-length link))
- (setf (slot-value link 'length) (link-tentative-length link)))))))
-
- ;; Algorithm: Find all the links whose endpoints don't match reality.
- ;; If the desired length is within their constraints, set their length to the
- ;; the desired length. If not, use the traversal functions to find the available
- ;; stretch or shrink along the paths from both linked contacts; if the stretch
- ;; or shrink is enough to accomodate the desired length, fake the partial resize
- ;; to adjust the affected children and links and set the link length to what's
- ;; left. If that still doesn't do it, return the list of unfixed links and
- ;; lengths, else return NIL.
- (defun adjust-sizes-to-fit (form)
- (let ((misfits (find-disturbed-links form))
- (unfixables nil))
- (when misfits
- (dolist (misfit misfits)
- (let ((link (car misfit))
- (desired-length (cdr misfit)))
- (if (length<= (link-minimum link)
- desired-length
- (link-maximum link))
- (setf (link-tentative-length link) desired-length)
- (if (< (link-length link) desired-length)
- (ecase (link-orientation link)
- (:horizontal
- (multiple-value-bind (from-stretch from-stretch-inf)
- (find-path-horizontal-stretch (link-from link) form)
- (multiple-value-bind (to-stretch to-stretch-inf)
- (find-path-horizontal-stretch (link-to link) form)
- ;; If there's enough stretch to do it, stretch them, else
- ;; stick the "misfit" entry on unfixables.
- (let* ((total-stretch (+ from-stretch to-stretch))
- (total-stretch-inf (+ from-stretch-inf to-stretch-inf))
- (total-diff (- desired-length (link-maximum link))))
- (if (and (zerop total-stretch-inf)
- (> total-diff total-stretch))
- (push misfit unfixables)
- (let ((from-diff (if (> total-stretch-inf 0)
- (if (zerop from-stretch-inf)
- 0
- (round (* total-diff
- (/ from-stretch-inf total-stretch-inf))))
- (round (* total-diff
- (/ from-stretch total-stretch)))))
- (to-diff (if (> total-stretch-inf 0)
- (if (zerop to-stretch-inf)
- 0
- (round (* total-diff
- (/ to-stretch-inf total-stretch-inf))))
- (round (* total-diff
- (/ to-stretch total-stretch))))))
- (resize-by-path-horizontal
- (link-from link) from-diff from-stretch from-stretch-inf form)
- (resize-by-path-horizontal
- (link-to link) to-diff to-stretch to-stretch-inf form t)
- (setf (link-tentative-length link) (link-maximum link)))))
- )))
- (:vertical
- (multiple-value-bind (from-stretch from-stretch-inf)
- (find-path-vertical-stretch (link-from link) form)
- (multiple-value-bind (to-stretch to-stretch-inf)
- (find-path-vertical-stretch (link-to link) form)
- ;; If there's enough stretch to do it, stretch them, else
- ;; stick the "misfit" entry on unfixables.
- (let* ((total-stretch (+ from-stretch to-stretch))
- (total-stretch-inf (+ from-stretch-inf to-stretch-inf))
- (total-diff (- desired-length (link-maximum link))))
- (if (and (zerop total-stretch-inf)
- (> total-diff total-stretch))
- (push misfit unfixables)
- (let ((from-diff (if (> total-stretch-inf 0)
- (if (zerop from-stretch-inf)
- 0
- (round (* total-diff
- (/ from-stretch-inf total-stretch-inf))))
- (round (* total-diff
- (/ from-stretch total-stretch)))))
- (to-diff (if (> total-stretch-inf 0)
- (if (zerop to-stretch-inf)
- 0
- (round (* total-diff
- (/ to-stretch-inf total-stretch-inf))))
- (round (* total-diff
- (/ to-stretch total-stretch))))))
- (resize-by-path-vertical
- (link-from link) from-diff from-stretch from-stretch-inf form)
- (resize-by-path-vertical
- (link-to link) to-diff to-stretch to-stretch-inf form t)
- (setf (link-tentative-length link) (link-maximum link)))))
- ))))
- (ecase (link-orientation link)
- (:horizontal
- (let* ((from-shrink (find-path-horizontal-shrink (link-from link) form))
- (to-shrink (find-path-horizontal-shrink (link-to link) form))
- (total-shrink (+ from-shrink to-shrink))
- (total-diff (- desired-length (link-minimum link))))
- ;; If there's enough shrink to do it, shrink them, else
- ;; stick the "misfit" entry on unfixables.
- (if (> (- total-diff) total-shrink)
- (push misfit unfixables)
- (let ((from-diff (round (* total-diff
- (/ from-shrink total-shrink))))
- (to-diff (round (* total-diff
- (/ to-shrink total-shrink)))))
- (resize-by-path-horizontal (link-from link) from-diff from-shrink 0 form)
- (resize-by-path-horizontal (link-to link) to-diff to-shrink 0 form t)
- (setf (link-tentative-length link) (link-minimum link)))))
- )
- (:vertical
- (let* ((from-shrink (find-path-vertical-shrink (link-from link) form))
- (to-shrink (find-path-vertical-shrink (link-to link) form))
- (total-shrink (+ from-shrink to-shrink))
- (total-diff (- desired-length (link-minimum link))))
- ;; If there's enough shrink to do it, shrink them, else
- ;; stick the "misfit" entry on unfixables.
- (if (> (- total-diff) total-shrink)
- (push misfit unfixables)
- (let ((from-diff (round (* total-diff
- (/ from-shrink total-shrink))))
- (to-diff (round (* total-diff
- (/ to-shrink total-shrink)))))
- (resize-by-path-vertical (link-from link) from-diff from-shrink 0 form)
- (resize-by-path-vertical (link-to link) to-diff to-shrink 0 form t)
- (setf (link-tentative-length link) (link-minimum link)))))
- )))))))
- unfixables))
-
- ;; The width part of the resize algorithm. Given the width difference, figure
- ;; how much to scale (using the traversal functions from above), then adjust
- ;; the children and the links in proportion to their maximum or minimum sizes.
- ;; If stretching, and there are :infinites in the maximum stretch, only contacts
- ;; and links with :infinite maximum sizes will be affected.
- (defun resize-children-horizontal (form width-difference)
- (with-slots (children) form
- (let* ((h-shrink-p (< width-difference 0))
- (h-scale nil)
- (h-scale-inf 0))
- (labels ((compute-delta (length max-length min-length)
- (cond ((and (zerop h-scale-inf) ; No change allowed.
- (zerop h-scale))
- 0)
- (h-shrink-p ; A shrink.
- (round (* width-difference
- (/ (- length min-length)
- h-scale))))
- ((zerop h-scale-inf) ; A stretch without :infinites.
- (round (* width-difference
- (/ (- max-length length)
- h-scale))))
- (:else ; A stretch with :infinites.
- (if (eq max-length :infinite)
- (round (/ width-difference
- h-scale-inf))
- 0))))
- (scale-horizontal-link (link)
- (let ((offset-delta (compute-delta (link-length link)
- (link-maximum link)
- (link-minimum link))))
- (unless (zerop offset-delta)
- (setf (link-tentative-length link) ; Constrain the length between its min and max.
- (max (length-min (+ (link-length link) offset-delta)
- (link-maximum link))
- (link-minimum link)))))))
-
- (if h-shrink-p
- (setq h-scale (find-form-horizontal-shrink form))
- (multiple-value-setq (h-scale h-scale-inf)
- (find-form-horizontal-stretch form)))
- (dolist (contact children)
- (let ((delta-w (compute-delta (contact-width contact)
- (form-max-width contact)
- (form-min-width contact))))
- (unless (zerop delta-w)
- (setf (contact-tentative-width contact) ; Constrain the width between min and max.
- (max (length-min (+ (contact-width contact) delta-w)
- (form-max-width contact))
- (form-min-width contact)))))
- (dolist (link (contact-constraint contact :horizontal-links))
- (when (eq contact (link-from link))
- (scale-horizontal-link link))))
- (dolist (link (form-horizontal-links form))
- (when (eq form (link-from link))
- (scale-horizontal-link link)))))))
-
- ;; The height part of the resize algorithm. Given the height difference, figure
- ;; how much to scale (using the traversal functions from above), then adjust
- ;; the children and the links in proportion to their maximum or minimum sizes.
- ;; If stretching, and there are :infinites in the maximum stretch, only contacts
- ;; and links with :infinite maximum sizes will be affected.
- (defun resize-children-vertical (form height-difference)
- (with-slots (children) form
- (let* ((v-shrink-p (< height-difference 0))
- (v-scale nil)
- (v-scale-inf 0))
- (labels ((compute-delta (length max-length min-length)
- (cond ((and (zerop v-scale-inf) ; No change allowed.
- (zerop v-scale))
- 0)
- (v-shrink-p ; A shrink.
- (round (* height-difference
- (/ (- length min-length)
- v-scale))))
- ((zerop v-scale-inf) ; A stretch without :infinites.
- (round (* height-difference
- (/ (- max-length length)
- v-scale))))
- (:else ; A stretch with :infinites.
- (if (eq max-length :infinite)
- (round (/ height-difference
- v-scale-inf))
- 0))))
- (scale-vertical-link (link)
- (let ((offset-delta (compute-delta (link-length link)
- (link-maximum link)
- (link-minimum link))))
- (unless (zerop offset-delta)
- (setf (link-tentative-length link) ; Keep length between link min and max.
- (max (length-min (+ (link-length link) offset-delta)
- (link-maximum link))
- (link-minimum link)))))))
-
- (if v-shrink-p
- (setq v-scale (find-form-vertical-shrink form))
- (multiple-value-setq (v-scale v-scale-inf)
- (find-form-vertical-stretch form)))
- (dolist (contact children)
- (let ((delta-h (compute-delta (contact-height contact)
- (form-max-height contact)
- (form-min-height contact))))
- (unless (zerop delta-h)
- (setf (contact-tentative-height contact) ; Keep height between min and max.
- (max (length-min (+ (contact-height contact) delta-h)
- (form-max-height contact))
- (form-min-height contact)))))
- (dolist (link (contact-constraint contact :vertical-links))
- (when (eq contact (link-from link))
- (scale-vertical-link link))))
- (dolist (link (form-vertical-links form))
- (when (eq form (link-from link))
- (scale-vertical-link link)))))))
-
-
- ;; Move children around, following the link graph, based on the current
- ;; (tentative) sizes and positions of contacts and links earlier in the
- ;; graph. This function and the next are a pair much like the traversal
- ;; functions: the first one operates on links attached to the Form, the
- ;; second on the paths from children contacts recursively through the link
- ;; graph to the Form again.
- (defun place-children-from-form-horizontal (form error-p)
- ;; Clear the ticks from last time.
- (dolist (contact (composite-children form))
- (setf (form-tick contact) nil))
-
- ;; For each link, the position of the contact on the other end is a
- ;; function of the current contact's position and size and the attach
- ;; points and length of the link.
- (dolist (link (form-horizontal-links form))
- (when (eq form (link-from link))
- (let* ((r-contact (link-to link))
- (new-x (+ (ecase (link-attach-from link)
- (:left 0)
- (:right (+ (form-projected-width form)
- (contact-border-width form)
- (contact-border-width form)))
- (:center (+ (round (form-projected-width form) 2)
- (contact-border-width form))))
- (- (ecase (link-attach-to link)
- (:left 0)
- (:right (+ (contact-tentative-width r-contact)
- (contact-border-width r-contact)
- (contact-border-width r-contact)))
- (:center (+ (round (contact-tentative-width r-contact) 2)
- (contact-border-width r-contact)))))
- (link-tentative-length link))))
- (when (/= new-x (contact-tentative-x r-contact))
- (cond ((and (form-tick r-contact) error-p)
- ;; Already moved once, with a different X.
- (error "Inconsistent horizontal links on contact ~S" r-contact))
- (:else
- (setf (contact-tentative-x r-contact) new-x))))
- (setf (form-tick r-contact) t)
- (place-children-from-links-horizontal r-contact form error-p))))
-
- ;; Now do the to-link graph.
- (dolist (link (form-horizontal-links form))
- (when (and (eq form (link-to link))
- (null (form-tick (link-from link))))
- (let* ((l-contact (link-from link))
- (new-x (+ (ecase (link-attach-to link)
- (:left 0)
- (:right (+ (form-projected-width form)
- (contact-border-width form)
- (contact-border-width form)))
- (:center (+ (round (form-projected-width form) 2)
- (contact-border-width form))))
- (- (ecase (link-attach-from link)
- (:left 0)
- (:right (+ (contact-tentative-width l-contact)
- (contact-border-width l-contact)
- (contact-border-width l-contact)))
- (:center (+ (round (contact-tentative-width l-contact) 2)
- (contact-border-width l-contact)))))
- (- (link-tentative-length link)))))
- (when (/= new-x (contact-tentative-x l-contact))
- (cond ((and (form-tick l-contact) error-p)
- ;; Already moved once, with a different X.
- (error "Inconsistent horizontal links on contact ~S" l-contact))
- (:else
- (setf (contact-tentative-x l-contact) new-x))))
- (setf (form-tick l-contact) t)
- (place-children-from-links-horizontal l-contact form error-p t)))))
-
- (defun place-children-from-links-horizontal (contact top-level-form error-p &optional to-links-p)
- (unless (eq contact top-level-form) ; Stop when hit the Form again.
- ;; For each link, the position of the contact on the other end is a
- ;; function of the current contact's position and size and the attach
- ;; points and length of the link.
- (dolist (link (contact-constraint contact :horizontal-links))
- (when (and (eq contact (if to-links-p
- (link-to link)
- (link-from link)))
- (not (eq (if to-links-p
- (link-from link)
- (link-to link))
- top-level-form)))
- (let* ((r-contact (if to-links-p (link-from link) (link-to link)))
- (new-x (+ (contact-tentative-x contact)
- (ecase (if to-links-p (link-attach-to link) (link-attach-from link))
- (:left 0)
- (:right (+ (contact-tentative-width contact)
- (contact-border-width contact)
- (contact-border-width contact)))
- (:center (+ (round (contact-tentative-width contact) 2)
- (contact-border-width contact))))
- (- (ecase (if to-links-p (link-attach-from link) (link-attach-to link))
- (:left 0)
- (:right (+ (contact-tentative-width r-contact)
- (contact-border-width r-contact)
- (contact-border-width r-contact)))
- (:center (+ (round (contact-tentative-width r-contact) 2)
- (contact-border-width r-contact)))))
- (if to-links-p
- (- (link-tentative-length link))
- (link-tentative-length link)))))
- (when (/= new-x (contact-tentative-x r-contact))
- (cond ((and (form-tick r-contact) error-p)
- ;; Already moved once, with a different X.
- (error "Inconsistent horizontal links on contact ~S" r-contact))
- (:else
- (setf (contact-tentative-x r-contact) new-x))))
- (setf (form-tick r-contact) t)
- (place-children-from-links-horizontal r-contact top-level-form error-p to-links-p))))))
-
-
- ;; Move the children vertically. This function and the next are also a pair like
- ;; the traversal functions (see comments at place-children-from-form-horizontal).
- (defun place-children-from-form-vertical (form error-p)
- ;; Clear the ticks from last time.
- (dolist (contact (composite-children form))
- (setf (form-tick contact) nil))
-
- ;; For each link, the position of the contact on the other end is a
- ;; function of the current contact's position and size and the attach
- ;; points and length of the link.
- (dolist (link (form-vertical-links form))
- (when (eq form (link-from link))
- (let* ((b-contact (link-to link))
- (new-y (+ (ecase (link-attach-from link)
- (:top 0)
- (:bottom (+ (form-projected-height form)
- (contact-border-width form)
- (contact-border-width form)))
- (:center (+ (round (form-projected-height form) 2)
- (contact-border-width form))))
- (- (ecase (link-attach-to link)
- (:top 0)
- (:bottom (+ (contact-tentative-height b-contact)
- (contact-border-width b-contact)
- (contact-border-width b-contact)))
- (:center (+ (round (contact-tentative-height b-contact) 2)
- (contact-border-width b-contact)))))
- (link-tentative-length link))))
- (when (/= new-y (contact-tentative-y b-contact))
- (cond ((and (form-tick b-contact) error-p)
- ;; Already moved once, with a different Y.
- (error "Inconsistent vertical links on contact ~S" b-contact))
- (:else
- (setf (contact-tentative-y b-contact) new-y))))
- (setf (form-tick b-contact) t)
- (place-children-from-links-vertical b-contact form error-p))))
-
- ;; Now do the to-link graph.
- (dolist (link (form-vertical-links form))
- (when (and (eq form (link-to link))
- (null (form-tick (link-from link))))
- (let* ((t-contact (link-from link))
- (new-y (+ (ecase (link-attach-to link)
- (:top 0)
- (:bottom (+ (form-projected-height form)
- (contact-border-width form)
- (contact-border-width form)))
- (:center (+ (round (form-projected-height form) 2)
- (contact-border-width form))))
- (- (ecase (link-attach-from link)
- (:top 0)
- (:bottom (+ (contact-tentative-height t-contact)
- (contact-border-width t-contact)
- (contact-border-width t-contact)))
- (:center (+ (round (contact-tentative-height t-contact) 2)
- (contact-border-width t-contact)))))
- (- (link-tentative-length link)))))
- (when (/= new-y (contact-tentative-y t-contact))
- (cond ((and (form-tick t-contact) error-p)
- ;; Already moved once, with a different Y.
- (error "Inconsistent vertical links on contact ~S" t-contact))
- (:else
- (setf (contact-tentative-y t-contact) new-y))))
- (setf (form-tick t-contact) t)
- (place-children-from-links-vertical t-contact form error-p t)))))
-
- (defun place-children-from-links-vertical (contact top-level-form error-p &optional to-links-p)
- (unless (eq contact top-level-form) ; Stop when hit the Form again.
- ;; For each link, the position of the contact on the other end is a
- ;; function of the current contact's position and size and the attach
- ;; points and length of the link.
- (dolist (link (contact-constraint contact :vertical-links))
- (when (and (eq contact (if to-links-p (link-to link) (link-from link)))
- (not (eq (if to-links-p
- (link-from link)
- (link-to link))
- top-level-form)))
- (let* ((b-contact (if to-links-p (link-from link) (link-to link)))
- (new-y (+ (contact-tentative-y contact)
- (ecase (if to-links-p (link-attach-to link) (link-attach-from link))
- (:top 0)
- (:bottom (+ (contact-tentative-height contact)
- (contact-border-width contact)
- (contact-border-width contact)))
- (:center (+ (round (contact-tentative-height contact) 2)
- (contact-border-width contact))))
- (- (ecase (if to-links-p (link-attach-from link) (link-attach-to link))
- (:top 0)
- (:bottom (+ (contact-tentative-height b-contact)
- (contact-border-width b-contact)
- (contact-border-width b-contact)))
- (:center (+ (round (contact-tentative-height b-contact) 2)
- (contact-border-width b-contact)))))
- (if to-links-p
- (- (link-tentative-length link))
- (link-tentative-length link)))))
- (when (/= new-y (contact-tentative-y b-contact))
- (cond ((and (form-tick b-contact) error-p)
- ;; Already moved once, with a different Y.
- (error "Inconsistent vertical links on contact ~S" b-contact))
- (:else
- (setf (contact-tentative-y b-contact) new-y))))
- (setf (form-tick b-contact) t)
- (place-children-from-links-vertical b-contact top-level-form error-p to-links-p))))))
-
- ;;;
- ;;; Two specialised traversal-based resize functions for adjust-sizes-to-fit.
-
- (defun resize-by-path-horizontal (contact width-difference h-scale h-scale-inf top-level-form &optional to-p)
- (unless (eq contact top-level-form)
- (let ((h-shrink-p (< width-difference 0)))
- (labels ((compute-delta (length max-length min-length)
- (cond ((and (zerop h-scale-inf) ; No change allowed.
- (zerop h-scale))
- 0)
- (h-shrink-p ; A shrink.
- (round (* width-difference
- (/ (- length min-length)
- h-scale))))
- ((zerop h-scale-inf) ; A stretch without :infinites.
- (round (* width-difference
- (/ (- max-length length)
- h-scale))))
- (:else ; A stretch with :infinites.
- (if (eq max-length :infinite)
- (round (/ width-difference
- h-scale-inf))
- 0))))
- (scale-horizontal-link (link)
- (let ((offset-delta (compute-delta (link-length link)
- (link-maximum link)
- (link-minimum link))))
- (unless (zerop offset-delta)
- (setf (link-tentative-length link) ; Constrain the length between its min and max.
- (max (length-min (+ (link-length link) offset-delta)
- (link-maximum link))
- (link-minimum link)))))))
- (let ((delta-w (compute-delta (contact-width contact)
- (form-max-width contact)
- (form-min-width contact))))
- (unless (zerop delta-w)
- (setf (contact-tentative-width contact) ; Constrain the width between min and max.
- (max (length-min (+ (contact-tentative-width contact) delta-w)
- (form-max-width contact))
- (form-min-width contact)))))
- (dolist (link (contact-constraint contact :horizontal-links))
- (let ((next-contact (if to-p (link-from link) (link-to link))))
- (unless (eq contact next-contact)
- (scale-horizontal-link link)
- (resize-by-path-horizontal next-contact width-difference h-scale h-scale-inf top-level-form to-p))))))))
-
- (defun resize-by-path-vertical (contact height-difference v-scale v-scale-inf top-level-form &optional to-p)
- (unless (eq contact top-level-form)
- (let ((v-shrink-p (< height-difference 0)))
- (labels ((compute-delta (length max-length min-length)
- (cond ((and (zerop v-scale-inf) ; No change allowed.
- (zerop v-scale))
- 0)
- (v-shrink-p ; A shrink.
- (round (* height-difference
- (/ (- length min-length)
- v-scale))))
- ((zerop v-scale-inf) ; A stretch without :infinites.
- (round (* height-difference
- (/ (- max-length length)
- v-scale))))
- (:else ; A stretch with :infinites.
- (if (eq max-length :infinite)
- (round (/ height-difference
- v-scale-inf))
- 0))))
- (scale-vertical-link (link)
- (let ((offset-delta (compute-delta (link-length link)
- (link-maximum link)
- (link-minimum link))))
- (unless (zerop offset-delta)
- (setf (link-tentative-length link) ; Constrain the length between its min and max.
- (max (length-min (+ (link-length link) offset-delta)
- (link-maximum link))
- (link-minimum link)))))))
- (let ((delta-h (compute-delta (contact-height contact)
- (form-max-height contact)
- (form-min-height contact))))
- (unless (zerop delta-h)
- (setf (contact-tentative-height contact) ; Constrain the height between min and max.
- (max (length-min (+ (contact-tentative-height contact) delta-h)
- (form-max-height contact))
- (form-min-height contact)))))
- (dolist (link (contact-constraint contact :vertical-links))
- (let ((next-contact (if to-p (link-from link) (link-to link))))
- (unless (eq contact next-contact)
- (scale-vertical-link link)
- (resize-by-path-vertical next-contact height-difference v-scale v-scale-inf top-level-form to-p))))))))
-
-